Haskell general programming with SYB and ad-hoc polymorphism

I have a class identical to Show , and I would like to make an instance of this class for each type of tuple. This is usually done by separately writing instances for each type of tuple.

instance (Show a, Show b) => Show (a,b) where showsPrec _ (a,b) s = show_tuple [shows a, shows b] s instance (Show a, Show b, Show c) => Show (a, b, c) where showsPrec _ (a,b,c) s = show_tuple [shows a, shows b, shows c] s instance (Show a, Show b, Show c, Show d) => Show (a, b, c, d) where showsPrec _ (a,b,c,d) s = show_tuple [shows a, shows b, shows c, shows d] s ... 

Writing one instance to one type of tuple leads to a large number of patterns and it is easy to see a common pattern common to all showPrec implementations. To avoid this kind of patterns, I thought that I could use Data.Generics from Scrap your pattern and implement tuple folding, for example

 showTuple = intercalate " " . gmapQ ("" `mkQ` show) 

But showTuple not working for any reason

 > showTuple (1,2) " " 

I think the problem is that show is polymorphic, because if I specialize in showTuple , then it works

 showTupleInt = intercalate " " . gmapQ ("" `mkQ` (show :: Int -> String)) 
 > showTupleInt (1::Int,2::Int) "1 2" 

I checked the gshow code that does something similar to what I need, but I cannot figure out how this works. If I try to import its code into GHCI, I get an error message:

 > let gshows = (\t -> showChar '(' . (showString . showConstr . toConstr $ t) . (foldr (.) id . gmapQ ((showChar ' ' .) . gshows) $ t) . showChar ')' ) `extQ` (shows :: String -> ShowS) <interactive>:262:59: Could not deduce (a ~ d) from the context (Data a) bound by the inferred type of gshows :: Data a => a -> String -> String at <interactive>:(259,5)-(264,44) or from (Data d) bound by a type expected by the context: Data d => d -> String -> String at <interactive>:262:33-65 `a' is a rigid type variable bound by the inferred type of gshows :: Data a => a -> String -> String at <interactive>:259:5 `d' is a rigid type variable bound by a type expected by the context: Data d => d -> String -> String at <interactive>:262:33 Expected type: d -> String -> String Actual type: a -> String -> String In the second argument of `(.)', namely `gshows' In the first argument of `gmapQ', namely `((showChar ' ' .) . gshows)' In the second argument of `(.)', namely `gmapQ ((showChar ' ' .) . gshows)' 

I have two questions:

  • what's wrong with showTuple and how can I fix it so that it works with tuples of any size
  • How does gshow work and why, if I import its code into GHCI, do I get this error?

EDIT: I am studying Data.Generics and generally SYM, so I would like to use this module. I will accept the answer only if it uses only this module. Thanks.

+6
source share
3 answers

I am more familiar with GHC Generics, not SYB, so I propose a solution based on Generics. Although this does not directly answer your question, I hope this can also be useful.

 {-# LANGUAGE TypeOperators, FlexibleContexts, DefaultSignatures #-} import Data.Sequence import GHC.Generics class Strs' f where strings' :: fa -> Seq String instance Strs' U1 where strings' U1 = empty instance Show c => Strs' (K1 ic) where strings' (K1 a) = singleton $ show a instance (Strs' a) => Strs' (M1 ica) where strings' (M1 a) = strings' a instance (Strs' f, Strs' g) => Strs' (f :*: g) where strings' (a :*: b) = strings' a >< strings' b class Strs a where strings :: a -> Seq String default strings :: (Generic a, Strs' (Rep a)) => a -> Seq String strings = strings' . from -- Since tuples have Generic instances, they're automatically derived using -- the above default. instance Strs () where instance (Show a, Show b) => Strs (a, b) where instance (Show a, Show b, Show c) => Strs (a, b, c) where 
+6
source

You are correct that showTuple does not work due to show polymorphism. The problem is that mkQ wants to single out one specific type:

 mkQ :: (Typeable a, Typeable b) => r -> (b -> r) -> a -> r 

b there must be one particular type in the type signature for each use of mkQ - without a type signature, the rules will probably select something (not sure what!), whereas with the type signature it chooses Int .

Your showTupleInt works with tuples of any size, but, of course, not with tuples of any type.

The problem with gshows in GHCi is that you actually need a type signature that allows you to enter validation, due to the recursive use of gshows in your own definition in another type of source call. Without a type signature, the type checker wants the gshows definition gshows have exactly the same instantiation of the type variable as using gshows , which appears as an error like Could not deduce (a ~ d) .

You can see this by placing it in the source file with and without signature type - with signatures that it distinguishes perfectly, without it you will get a similar error with the one you received if you first use :set -XNoMonomorphismRestriction .

gshows works generally because of the gmapQ type:

 gmapQ :: Data a => (forall d. Data d => d -> u) -> a -> [u] 

Unlike mkQ , this parameter itself is polymorphic - pay attention to the nested forall .

Although your showTuple also uses gmapQ , it's too late - mkQ has already caused a problem, making show work with only one type.

You also cannot directly use show directly with gmapQ , because the restrictions are different - gmapQ wants something that will work on any instance of Data , while show limited to show , gshows never uses a class like show in general, although it does use shows specialized for String .

It is hard to prove a negation in such a case, but I'm sure you cannot write something like showTuple that will use the show class polymorphically using only syb , because it just has nothing that can "recognize" types that have specific instance. This is why syb-with-class exists.

Also, if you really want something that only works at the same level of the type structure, that is, it shows any size tuple, but uses something else for the tuple elements, then syb is probably the wrong solution, as it is intended for operational recursion and search of things at any level of the data structure. My opinion is that the GHC.Generics solution is the most enjoyable for implementing showTuple .

+5
source

You can use syb-with-class . It precedes -XConstraintKinds , so you need to write an instance of the Sat class and get the data class that this library receives. Here is an example that is pretty close to the showTuple example, except that I add {}:

 {-# LANGUAGE FlexibleContexts, FlexibleInstances, MultiParamTypeClasses, TemplateHaskell, UndecidableInstances #-} import Data.Generics.SYB.WithClass.Basics import Data.Generics.SYB.WithClass.Instances import Data.Generics.SYB.WithClass.Derive data A abc = A abc deriving Show data B a = B a deriving Show data C a = C a deriving Show derive [''A,''B,''C] data ShowD a = ShowD { showD :: a -> String -> String } instance (Show a) => Sat (ShowD a) where dict = ShowD shows gshow x = case gfoldl ctx (\ (s, f) x -> (s . ("{"++) . showD dict x . ("}"++) , fx)) (\y -> (id ,y)) x of (str,_) -> str "" where ctx :: Proxy ShowD ctx = undefined x1 = A (B 'b') (C "abc") (B ()) {- >>> gshow x1 "{B 'b'}{C \"abc\"}{B ()}" >>> show x1 "A (B 'b') (C \"abc\") (B ())" -} 

The second argument to gfoldl receives a call to shows (B 'b') , shows (C "abc") and shows (B ()) thanks to showD dict , which receives the shows function with the correct type.

+5
source

All Articles