Datatype-Generic Programming in Haskell (pdf) - of Andres Löh
Datatype-Generic Programming in Haskell (pdf) - of Andres Löh Datatype-Generic Programming in Haskell (pdf) - of Andres Löh
Datatype-Generic Programming in Haskell Andres Löh (thanks to José Pedro Magalhães, Simon Peyton Jones and many others) Skills Matter “In the Brain” – 9 April 2013 Copyright © 2013 Well-Typed LLP . .Well-Typed .The Haskell Consultants
- Page 2 and 3: Datatypes are great ◮ Easy to int
- Page 4 and 5: Derivable classes In Haskell 2010:
- Page 6 and 7: What about other classes? For many
- Page 8 and 9: GHC Generic support From the user p
- Page 10 and 11: Analyzing deriving
- Page 12 and 13: Equality on binary trees data T = L
- Page 14 and 15: Equality on another type data Choic
- Page 16 and 17: What is the pattern? ◮ How many c
- Page 18 and 19: More datatypes data Tree a = Leaf a
- Page 20 and 21: Yet another equality function This
- Page 22 and 23: More concepts ◮ Parameterization
- Page 24 and 25: The equality pattern An informal de
- Page 26 and 27: Isomorphism between types Two types
- Page 28 and 29: Example Lists and Snoc-lists are is
- Page 30 and 31: The idea of datatype-generic progra
- Page 32 and 33: The idea of datatype-generic progra
- Page 34 and 35: Choice between constructors Which t
- Page 36 and 37: Choice between constructors Which t
- Page 38 and 39: Choice between constructors Which t
- Page 40 and 41: Combining constructor fields Which
- Page 42 and 43: Combining constructor fields Which
- Page 44 and 45: What about constructors without arg
- Page 46 and 47: Representing types
- Page 48 and 49: Representing types To keep represen
- Page 50 and 51: A class for representable types cla
<strong>Datatype</strong>-<strong>Generic</strong> <strong>Programm<strong>in</strong>g</strong> <strong>in</strong> <strong>Haskell</strong><br />
<strong>Andres</strong> <strong>Löh</strong><br />
(thanks to José Pedro Magalhães, Simon Peyton Jones and many others)<br />
Skills Matter “In the Bra<strong>in</strong>” – 9 April 2013<br />
Copyright © 2013 Well-Typed LLP<br />
.<br />
.Well-Typed<br />
.The <strong>Haskell</strong> Consultants
<strong>Datatype</strong>s are great<br />
◮ Easy to <strong>in</strong>troduce.<br />
◮ Dist<strong>in</strong>guished from exist<strong>in</strong>g types by the compiler.<br />
◮ Added safety.<br />
◮ Can use doma<strong>in</strong>-specific names for types and<br />
constructors.<br />
◮ Quite readable.<br />
.<br />
.Well-Typed
<strong>Datatype</strong>s are not so great<br />
◮ New datatypes have no associated library.<br />
◮ Cannot be compared for equality, cannot be (de)serialized,<br />
cannot be traversed, . . .<br />
Fortunately, there is deriv<strong>in</strong>g .<br />
.<br />
.Well-Typed
Derivable classes<br />
In <strong>Haskell</strong> 2010:<br />
Eq , Ord , Enum , Bounded , Read , Show<br />
.<br />
.Well-Typed
Derivable classes<br />
In <strong>Haskell</strong> 2010:<br />
Eq , Ord , Enum , Bounded , Read , Show<br />
In GHC (<strong>in</strong> addition to the ones above):<br />
Functor , Traversable , Typeable , Data , <strong>Generic</strong><br />
.<br />
.Well-Typed
What about other classes?<br />
For many additional classes, we can <strong>in</strong>tuitively derive <strong>in</strong>stances.<br />
But can we also do it <strong>in</strong> practice?<br />
.<br />
.Well-Typed
What about other classes?<br />
For many additional classes, we can <strong>in</strong>tuitively derive <strong>in</strong>stances.<br />
But can we also do it <strong>in</strong> practice?<br />
Options:<br />
◮ use an external preprocessor,<br />
◮ use Template <strong>Haskell</strong>,<br />
◮ use data-derive,<br />
◮ or use the GHC <strong>Generic</strong> support.<br />
.<br />
.Well-Typed
GHC <strong>Generic</strong> support<br />
From the user perspective:<br />
Step 1<br />
Def<strong>in</strong>e a new datatype and derive <strong>Generic</strong> for it.<br />
data MyType a b =<br />
Flag Bool | Combo (a, a) | Other b Int (MyType a a)<br />
deriv<strong>in</strong>g <strong>Generic</strong><br />
.<br />
.Well-Typed
GHC <strong>Generic</strong> support<br />
From the user perspective:<br />
Step 2<br />
Use a library that makes use <strong>of</strong> GHC <strong>Generic</strong> and give an<br />
empty <strong>in</strong>stance declaration for a suitable type class:<br />
import Data.B<strong>in</strong>ary<br />
...<br />
<strong>in</strong>stance (B<strong>in</strong>ary a, B<strong>in</strong>ary b) ⇒ B<strong>in</strong>ary (MyType a b)<br />
.<br />
.Well-Typed
Analyz<strong>in</strong>g deriv<strong>in</strong>g
Equality as an example<br />
class Eq ′ a where<br />
eq :: a → a → Bool<br />
Let’s def<strong>in</strong>e some <strong>in</strong>stances by hand.<br />
.<br />
.Well-Typed
Equality on b<strong>in</strong>ary trees<br />
data T = L | N T T<br />
<strong>in</strong>stance Eq ′ T where<br />
eq L L = True<br />
eq (N x1 y1) (N x2 y2) = eq x1 x2 && eq y1 y2<br />
eq = False<br />
.<br />
.Well-Typed
Equality on another type<br />
data Choice = I Int | C Char | B Choice Bool | S Choice<br />
.<br />
.Well-Typed
Equality on another type<br />
data Choice = I Int | C Char | B Choice Bool | S Choice<br />
Assum<strong>in</strong>g <strong>in</strong>stances for Int , Char , Bool :<br />
<strong>in</strong>stance Eq ′ Choice where<br />
eq (I n1 ) (I n2 ) = eq n1 n2<br />
eq (C c1 ) (C c2 ) = eq c1 c2<br />
eq (B x1 b1) (B x2 b2) = eq x1 x2 &&<br />
eq b1 b2<br />
eq (S x1 ) (S x2 ) = eq x1 x2<br />
eq = False<br />
.<br />
.Well-Typed
What is the pattern?<br />
◮ How many cases does the function def<strong>in</strong>ition have?<br />
◮ What is on the right hand sides?<br />
.<br />
.Well-Typed
What is the pattern?<br />
◮ How many cases does the function def<strong>in</strong>ition have?<br />
◮ What is on the right hand sides?<br />
◮ How many clauses are there <strong>in</strong> the conjunctions on each<br />
right hand side?<br />
.<br />
.Well-Typed
What is the pattern?<br />
◮ How many cases does the function def<strong>in</strong>ition have?<br />
◮ What is on the right hand sides?<br />
◮ How many clauses are there <strong>in</strong> the conjunctions on each<br />
right hand side?<br />
Relevant concepts:<br />
◮ number <strong>of</strong> constructors <strong>in</strong> datatype,<br />
◮ number <strong>of</strong> fields per constructor,<br />
◮ recursion leads to recursion,<br />
◮ other types lead to <strong>in</strong>vocation <strong>of</strong> equality on those types.<br />
.<br />
.Well-Typed
More datatypes<br />
data Tree a = Leaf a | Node (Tree a) (Tree a)<br />
Like before, but with labels <strong>in</strong> the leaves.<br />
.<br />
.Well-Typed
More datatypes<br />
data Tree a = Leaf a | Node (Tree a) (Tree a)<br />
Like before, but with labels <strong>in</strong> the leaves.<br />
<strong>in</strong>stance Eq ′ a ⇒ Eq ′ (Tree a) where<br />
eq (Leaf n1 ) (Leaf n2 ) = eq n1 n2<br />
eq (Node x1 y1) (Node x2 y2) = eq x1 x2 && eq y1 y2<br />
eq = False<br />
.<br />
.Well-Typed
Yet another equality function<br />
This is <strong>of</strong>ten called a rose tree:<br />
data Rose a = Fork a [Rose a]<br />
.<br />
.Well-Typed
Yet another equality function<br />
This is <strong>of</strong>ten called a rose tree:<br />
data Rose a = Fork a [Rose a]<br />
Assum<strong>in</strong>g an <strong>in</strong>stance for lists:<br />
<strong>in</strong>stance Eq ′ a ⇒ Eq ′ (Rose a) where<br />
eq (Fork x1 xs1) (Fork x2 xs2) = eq x1 x2 && eq xs1 xs2<br />
.<br />
.Well-Typed
More concepts<br />
◮ Parameterization <strong>of</strong> types is reflected by parameterization<br />
<strong>of</strong> the functions (via constra<strong>in</strong>ts on the <strong>in</strong>stances).<br />
◮ Us<strong>in</strong>g parameterized types <strong>in</strong> other types then works as<br />
expected.<br />
.<br />
.Well-Typed
The equality pattern<br />
An <strong>in</strong>formal description<br />
In order to def<strong>in</strong>e equality for a datatype:<br />
◮ <strong>in</strong>troduce a parameter for each parameter <strong>of</strong> the datatype,<br />
◮ <strong>in</strong>troduce a case for each constructor <strong>of</strong> the datatype,<br />
◮ <strong>in</strong>troduce a f<strong>in</strong>al catch-all case return<strong>in</strong>g False ,<br />
◮ for each <strong>of</strong> the other cases, compare the constructor fields<br />
pair-wise and comb<strong>in</strong>e them us<strong>in</strong>g (&&) ,<br />
◮ for each field, use the appropriate equality <strong>in</strong>stance.<br />
.<br />
.Well-Typed
The equality pattern<br />
An <strong>in</strong>formal description<br />
In order to def<strong>in</strong>e equality for a datatype:<br />
◮ <strong>in</strong>troduce a parameter for each parameter <strong>of</strong> the datatype,<br />
◮ <strong>in</strong>troduce a case for each constructor <strong>of</strong> the datatype,<br />
◮ <strong>in</strong>troduce a f<strong>in</strong>al catch-all case return<strong>in</strong>g False ,<br />
◮ for each <strong>of</strong> the other cases, compare the constructor fields<br />
pair-wise and comb<strong>in</strong>e them us<strong>in</strong>g (&&) ,<br />
◮ for each field, use the appropriate equality <strong>in</strong>stance.<br />
If we can describe it, can we write a program to do it?<br />
.<br />
.Well-Typed
Interlude:<br />
type isomorphisms
Isomorphism between types<br />
Two types A and B are called isomorphic if we have functions<br />
f :: A → B<br />
g :: B → A<br />
that are mutual <strong>in</strong>verses, i.e., if<br />
f ◦ g ≡ id<br />
g ◦ f ≡ id<br />
.<br />
.Well-Typed
Example<br />
Lists and Snoc-lists are isomorphic<br />
data SnocList a = L<strong>in</strong> | SnocList a :> a<br />
.<br />
.Well-Typed
Example<br />
Lists and Snoc-lists are isomorphic<br />
data SnocList a = L<strong>in</strong> | SnocList a :> a<br />
listToSnocList :: [a] → SnocList a<br />
listToSnocList [ ] = L<strong>in</strong><br />
listToSnocList (x : xs) = listToSnocList xs :> x<br />
snocListToList :: SnocList a → [a]<br />
snocListToList L<strong>in</strong> = [ ]<br />
snocListToList (xs :> x ) = x : snocListToList xs<br />
We can (but won’t) prove that these are <strong>in</strong>verses.<br />
.<br />
.Well-Typed
The idea <strong>of</strong> datatype-generic programm<strong>in</strong>g<br />
◮ Represent a type A as an isomorphic type Rep A .<br />
.<br />
.Well-Typed
The idea <strong>of</strong> datatype-generic programm<strong>in</strong>g<br />
◮ Represent a type A as an isomorphic type Rep A .<br />
◮ If a limited number <strong>of</strong> type constructors is used to build<br />
Rep A ,<br />
.<br />
.Well-Typed
The idea <strong>of</strong> datatype-generic programm<strong>in</strong>g<br />
◮ Represent a type A as an isomorphic type Rep A .<br />
◮ If a limited number <strong>of</strong> type constructors is used to build<br />
Rep A ,<br />
◮ then functions def<strong>in</strong>ed on each <strong>of</strong> these type constructors<br />
.<br />
.Well-Typed
The idea <strong>of</strong> datatype-generic programm<strong>in</strong>g<br />
◮ Represent a type A as an isomorphic type Rep A .<br />
◮ If a limited number <strong>of</strong> type constructors is used to build<br />
Rep A ,<br />
◮ then functions def<strong>in</strong>ed on each <strong>of</strong> these type constructors<br />
◮ can be lifted to work on the orig<strong>in</strong>al type A<br />
.<br />
.Well-Typed
The idea <strong>of</strong> datatype-generic programm<strong>in</strong>g<br />
◮ Represent a type A as an isomorphic type Rep A .<br />
◮ If a limited number <strong>of</strong> type constructors is used to build<br />
Rep A ,<br />
◮ then functions def<strong>in</strong>ed on each <strong>of</strong> these type constructors<br />
◮ can be lifted to work on the orig<strong>in</strong>al type A<br />
◮ and thus on any representable type.<br />
.<br />
.Well-Typed
Choice between constructors<br />
Which type best encodes choice between constructors?<br />
.<br />
.Well-Typed
Choice between constructors<br />
Which type best encodes choice between constructors?<br />
Well, let’s restrict to two constructors first.<br />
.<br />
.Well-Typed
Choice between constructors<br />
Which type best encodes choice between constructors?<br />
Well, let’s restrict to two constructors first.<br />
Booleans encode choice, but do not provide <strong>in</strong>formation what<br />
the choice is about.<br />
.<br />
.Well-Typed
Choice between constructors<br />
Which type best encodes choice between constructors?<br />
Well, let’s restrict to two constructors first.<br />
Booleans encode choice, but do not provide <strong>in</strong>formation what<br />
the choice is about.<br />
data Either a b = Left a | Right b<br />
.<br />
.Well-Typed
Choice between constructors<br />
Which type best encodes choice between constructors?<br />
Well, let’s restrict to two constructors first.<br />
Booleans encode choice, but do not provide <strong>in</strong>formation what<br />
the choice is about.<br />
data Either a b = Left a | Right b<br />
Choice between three th<strong>in</strong>gs:<br />
type Either3 a b c = Either a (Either b c)<br />
.<br />
.Well-Typed
Comb<strong>in</strong><strong>in</strong>g constructor fields<br />
Which type best encodes comb<strong>in</strong><strong>in</strong>g fields?<br />
.<br />
.Well-Typed
Comb<strong>in</strong><strong>in</strong>g constructor fields<br />
Which type best encodes comb<strong>in</strong><strong>in</strong>g fields?<br />
Aga<strong>in</strong>, let’s just consider two <strong>of</strong> them.<br />
.<br />
.Well-Typed
Comb<strong>in</strong><strong>in</strong>g constructor fields<br />
Which type best encodes comb<strong>in</strong><strong>in</strong>g fields?<br />
Aga<strong>in</strong>, let’s just consider two <strong>of</strong> them.<br />
data (a, b) = (a, b)<br />
.<br />
.Well-Typed
Comb<strong>in</strong><strong>in</strong>g constructor fields<br />
Which type best encodes comb<strong>in</strong><strong>in</strong>g fields?<br />
Aga<strong>in</strong>, let’s just consider two <strong>of</strong> them.<br />
data (a, b) = (a, b)<br />
Comb<strong>in</strong><strong>in</strong>g three fields:<br />
type Triple a b c = (a, (b, c))<br />
.<br />
.Well-Typed
What about constructors without arguments?<br />
We need another type.<br />
.<br />
.Well-Typed
What about constructors without arguments?<br />
We need another type.<br />
Well, how many values does a constructor without argument<br />
encode?<br />
.<br />
.Well-Typed
What about constructors without arguments?<br />
We need another type.<br />
Well, how many values does a constructor without argument<br />
encode?<br />
data () = ()<br />
.<br />
.Well-Typed
Represent<strong>in</strong>g types
Represent<strong>in</strong>g types<br />
To keep representation and orig<strong>in</strong>al types apart, let’s def<strong>in</strong>e<br />
isomorphic copies <strong>of</strong> the types we need:<br />
data U = U<br />
data a :+: b = L a | R b<br />
data a :∗: b = a :∗: b<br />
.<br />
.Well-Typed
Represent<strong>in</strong>g types<br />
To keep representation and orig<strong>in</strong>al types apart, let’s def<strong>in</strong>e<br />
isomorphic copies <strong>of</strong> the types we need:<br />
data U = U<br />
data a :+: b = L a | R b<br />
data a :∗: b = a :∗: b<br />
We can now get started:<br />
data Bool = False | True<br />
How do we represent Bool ?<br />
.<br />
.Well-Typed
Represent<strong>in</strong>g types<br />
To keep representation and orig<strong>in</strong>al types apart, let’s def<strong>in</strong>e<br />
isomorphic copies <strong>of</strong> the types we need:<br />
data U = U<br />
data a :+: b = L a | R b<br />
data a :∗: b = a :∗: b<br />
We can now get started:<br />
data Bool = False | True<br />
How do we represent Bool ?<br />
type RepBool = U :+: U<br />
.<br />
.Well-Typed
A class for representable types<br />
class <strong>Generic</strong> a where<br />
type Rep a<br />
from :: a → Rep a<br />
to :: Rep a → a<br />
The type Rep is an associated type.<br />
.<br />
.Well-Typed
A class for representable types<br />
class <strong>Generic</strong> a where<br />
type Rep a<br />
from :: a → Rep a<br />
to :: Rep a → a<br />
The type Rep is an associated type.<br />
Equivalent to def<strong>in</strong><strong>in</strong>g Rep separately as a type family:<br />
type family Rep a<br />
.<br />
.Well-Typed
Representable Booleans<br />
<strong>in</strong>stance <strong>Generic</strong> Bool where<br />
type Rep Bool = U :+: U<br />
from False = L U<br />
from True = R U<br />
to (L U) = False<br />
to (R U) = True<br />
.<br />
.Well-Typed
Representable lists<br />
<strong>in</strong>stance <strong>Generic</strong> [a] where<br />
type Rep [a] = U :+: (a :∗: [a])<br />
from [ ] = L U<br />
from (x : xs) = R (x :∗: xs)<br />
to (L U ) = [ ]<br />
to (R (x :∗: xs)) = x : xs<br />
.<br />
.Well-Typed
Representable lists<br />
<strong>in</strong>stance <strong>Generic</strong> [a] where<br />
type Rep [a] = U :+: (a :∗: [a])<br />
from [ ] = L U<br />
from (x : xs) = R (x :∗: xs)<br />
to (L U ) = [ ]<br />
to (R (x :∗: xs)) = x : xs<br />
Note:<br />
◮ shallow transformation,<br />
◮ no constra<strong>in</strong>t on <strong>Generic</strong> a required.<br />
.<br />
.Well-Typed
Representable trees<br />
<strong>in</strong>stance <strong>Generic</strong> (Tree a) where<br />
type Rep (Tree a) = a :+: (Tree a :∗: Tree a)<br />
from (Leaf n ) = L n<br />
from (Node x y ) = R (x :∗: y)<br />
to (L n ) = Leaf n<br />
to (R (x :∗: y)) = Node x y<br />
.<br />
.Well-Typed
Representable rose trees<br />
<strong>in</strong>stance <strong>Generic</strong> (Rose a) where<br />
type Rep (Rose a) = a :∗: [Rose a]<br />
from (Fork x xs) = x :∗: xs<br />
to (x :∗: xs ) = Fork x xs<br />
.<br />
.Well-Typed
Represent<strong>in</strong>g primitive types<br />
We don’t . . .<br />
.<br />
.Well-Typed
Back to equality
Intermediate summary<br />
◮ We have def<strong>in</strong>ed class <strong>Generic</strong> that maps datatypes to<br />
representations built up from U , (:+:) , (:∗:) and other<br />
datatypes.<br />
◮ If we can def<strong>in</strong>e equality on the representation types, then<br />
we should be able to obta<strong>in</strong> a generic equality function.<br />
◮ Let us apply the <strong>in</strong>formal recipe from earlier.<br />
.<br />
.Well-Typed
A class for generic equality<br />
class GEq a where<br />
geq :: a → a → Bool<br />
.<br />
.Well-Typed
Instance for sums<br />
<strong>in</strong>stance (GEq a, GEq b) ⇒ GEq (a :+: b) where<br />
geq (L a1) (L a2) = geq a1 a2<br />
geq (R b1) (R b2) = geq b1 b2<br />
geq = False<br />
.<br />
.Well-Typed
Instance for products and unit<br />
<strong>in</strong>stance (GEq a, GEq b) ⇒ GEq (a :∗: b) where<br />
geq (a1 :∗: b1) (a2 :∗: b2) = geq a1 a2 && geq b1 b2<br />
<strong>in</strong>stance GEq U where<br />
geq U U = True<br />
.<br />
.Well-Typed
Instances for primitive types<br />
<strong>in</strong>stance GEq Int where<br />
geq = ((= =) :: Int → Int → Bool)<br />
.<br />
.Well-Typed
What now?
Dispatch<strong>in</strong>g to the representation type<br />
defaultEq :: (<strong>Generic</strong> a, GEq (Rep a)) ⇒ a → a → Bool<br />
defaultEq x y = geq (from x) (from y)<br />
.<br />
.Well-Typed
Dispatch<strong>in</strong>g to the representation type<br />
defaultEq :: (<strong>Generic</strong> a, GEq (Rep a)) ⇒ a → a → Bool<br />
defaultEq x y = geq (from x) (from y)<br />
Def<strong>in</strong><strong>in</strong>g generic <strong>in</strong>stances is now trivial:<br />
<strong>in</strong>stance GEq Bool where<br />
geq = defaultEq<br />
<strong>in</strong>stance GEq a ⇒ GEq [a] where<br />
geq = defaultEq<br />
<strong>in</strong>stance GEq a ⇒ GEq (Tree a) where<br />
geq = defaultEq<br />
<strong>in</strong>stance GEq a ⇒ GEq (Rose a) where<br />
geq = defaultEq<br />
.<br />
.Well-Typed
Dispatch<strong>in</strong>g to the representation type<br />
defaultEq :: (<strong>Generic</strong> a, GEq (Rep a)) ⇒ a → a → Bool<br />
defaultEq x y = geq (from x) (from y)<br />
Or with the DefaultSignatures language extension:<br />
class GEq a where<br />
geq :: a → a → Bool<br />
default geq :: (<strong>Generic</strong> a, GEq (Rep a)) ⇒ a → a → Bool<br />
geq = defaultEq<br />
<strong>in</strong>stance GEq Bool<br />
<strong>in</strong>stance GEq a ⇒ GEq [a]<br />
<strong>in</strong>stance GEq a ⇒ GEq (Tree a)<br />
<strong>in</strong>stance GEq a ⇒ GEq (Rose a)<br />
.<br />
.Well-Typed
Isn’t this as bad as before?
Amount <strong>of</strong> work<br />
Question<br />
Haven’t we just replaced some tedious work (def<strong>in</strong><strong>in</strong>g equality<br />
for a type) by some other tedious work (def<strong>in</strong><strong>in</strong>g a<br />
representation for a type)?<br />
.<br />
.Well-Typed
Amount <strong>of</strong> work<br />
Question<br />
Haven’t we just replaced some tedious work (def<strong>in</strong><strong>in</strong>g equality<br />
for a type) by some other tedious work (def<strong>in</strong><strong>in</strong>g a<br />
representation for a type)?<br />
Yes, but:<br />
◮ The representation has to be given only once, and works<br />
for potentially many generic functions.<br />
◮ S<strong>in</strong>ce there is a s<strong>in</strong>gle representation per type, it could be<br />
generated automatically by some other means (compiler<br />
support, TH).<br />
◮ In other words, it’s sufficient if we can use deriv<strong>in</strong>g on<br />
class <strong>Generic</strong> .<br />
.<br />
.Well-Typed
So can we derive <strong>Generic</strong> ?<br />
.<br />
.Well-Typed
So can we derive <strong>Generic</strong> ?<br />
Yes (with Derive<strong>Generic</strong>) . . .<br />
.<br />
.Well-Typed
So can we derive <strong>Generic</strong> ?<br />
Yes (with Derive<strong>Generic</strong>) . . .<br />
. . . but the representations are not quite as simple as we’ve<br />
pretended before:<br />
class <strong>Generic</strong> a where<br />
type Rep a<br />
from :: a → Rep a<br />
to :: Rep a → a<br />
.<br />
.Well-Typed
So can we derive <strong>Generic</strong> ?<br />
Yes (with Derive<strong>Generic</strong>) . . .<br />
. . . but the representations are not quite as simple as we’ve<br />
pretended before:<br />
class <strong>Generic</strong> a where<br />
type Rep a :: ∗ → ∗<br />
from :: a → Rep a x<br />
to :: Rep a x → a<br />
Representation types are actually <strong>of</strong> k<strong>in</strong>d ∗ → ∗ .<br />
.<br />
.Well-Typed
An extra argument?<br />
◮ It’s a pragmatic choice.<br />
◮ Facilitates some th<strong>in</strong>gs, because we also want to derive<br />
classes parameterized by type constructors (such as<br />
Functor ).<br />
◮ For now, let’s just try to “ignore” the extra argument.<br />
.<br />
.Well-Typed
Simple vs. GHC representation<br />
Old:<br />
type <strong>in</strong>stance Rep (Tree a) = a :+: (Tree a :∗: Tree a)<br />
New:<br />
type <strong>in</strong>stance Rep (Tree a) =<br />
M1 D D1Tree<br />
(M1 C C1_0Tree<br />
(M1 S NoSelector (K1 P a))<br />
:+:<br />
M1 C C1_1Tree<br />
(M1 S NoSelector (K1 R (Tree a))<br />
:∗:<br />
M1 S NoSelector (K1 R (Tree a))<br />
)<br />
)<br />
.<br />
.Well-Typed
Simple vs. GHC representation<br />
Old:<br />
type <strong>in</strong>stance Rep (Tree a) = a :+: (Tree a :∗: Tree a)<br />
New:<br />
type <strong>in</strong>stance Rep (Tree a) =<br />
:+:<br />
( Tree a<br />
:∗:<br />
Tree a<br />
)<br />
a<br />
.<br />
.Well-Typed
Familiar components<br />
Everyth<strong>in</strong>g is now lifted to k<strong>in</strong>d ∗ → ∗ :<br />
data U1 a = U1<br />
data (f :+: g) a = L1 (f a) | R1 (g a)<br />
data (f :∗: g) a = f a :∗: g a<br />
.<br />
.Well-Typed
Wrapp<strong>in</strong>g constant types<br />
This is an extra type constructor wrapp<strong>in</strong>g every constant type:<br />
newtype K1 t c a = K1 {unK1 :: c}<br />
data P -- marks parameters<br />
data R -- marks other occurrences<br />
The first argument t is not used on the right hand side. It is<br />
supposed to be <strong>in</strong>stantiated with either P or R .<br />
.<br />
.Well-Typed
Meta <strong>in</strong>formation<br />
newtype M1 t i f a = M1 {unM1 :: f a}<br />
data D -- marks datatypes<br />
data C -- marks constructors<br />
data S -- marks (record) selectors<br />
Depend<strong>in</strong>g on the tag t , the position i is to be filled with a<br />
datatype belong<strong>in</strong>g to class <strong>Datatype</strong> , Constructor , or<br />
Selector .<br />
.<br />
.Well-Typed
Meta <strong>in</strong>formation – contd.<br />
class <strong>Datatype</strong> d where<br />
datatypeName :: w d f a → Str<strong>in</strong>g<br />
moduleName :: w d f a → Str<strong>in</strong>g<br />
.<br />
.Well-Typed
Meta <strong>in</strong>formation – contd.<br />
class <strong>Datatype</strong> d where<br />
datatypeName :: w d f a → Str<strong>in</strong>g<br />
moduleName :: w d f a → Str<strong>in</strong>g<br />
<strong>in</strong>stance <strong>Datatype</strong> D1Tree where<br />
datatypeName = "Tree"<br />
moduleName = ...<br />
Similarly for constructors.<br />
.<br />
.Well-Typed
Adapt<strong>in</strong>g the equality class(es)<br />
Works on representation types:<br />
class GEq ′ f where<br />
geq ′ :: f a → f a → Bool<br />
Works on “normal” types:<br />
class GEq a where<br />
geq :: a → a → Bool<br />
default geq :: (<strong>Generic</strong> a, GEq ′ (Rep a)) ⇒ a → a → Bool<br />
geq x y = geq ′ (from x) (from y)<br />
Instance for GEq Int and other primitive types as before.<br />
.<br />
.Well-Typed
Adapt<strong>in</strong>g the equality class(es) – contd.<br />
<strong>in</strong>stance (GEq ′ f, GEq ′ g) ⇒ GEq ′ (f :+: g) where<br />
geq ′ (L1 x) (L1 y) = geq ′ x y<br />
geq ′ (R1 x) (R1 y) = geq ′ x y<br />
geq ′ = False<br />
Similarly for :∗: and U1 .<br />
.<br />
.Well-Typed
Adapt<strong>in</strong>g the equality class(es) – contd.<br />
<strong>in</strong>stance (GEq ′ f, GEq ′ g) ⇒ GEq ′ (f :+: g) where<br />
geq ′ (L1 x) (L1 y) = geq ′ x y<br />
geq ′ (R1 x) (R1 y) = geq ′ x y<br />
geq ′ = False<br />
Similarly for :∗: and U1 .<br />
An <strong>in</strong>stance for constant types:<br />
<strong>in</strong>stance GEq a ⇒ GEq ′ (K1 t a) where<br />
geq ′ (K1 x) (K1 y) = geq x y<br />
.<br />
.Well-Typed
Adapt<strong>in</strong>g the equality classes – contd.<br />
For equality, we ignore all meta <strong>in</strong>formation:<br />
<strong>in</strong>stance GEq ′ f ⇒ GEq ′ (M1 t i f) where<br />
geq ′ (M1 x) (M1 y) = geq ′ x y<br />
All meta <strong>in</strong>formation is grouped under a s<strong>in</strong>gle datatype, so that<br />
we can easily ignore it all if we want to.<br />
.<br />
.Well-Typed
Adapt<strong>in</strong>g the equality classes – contd.<br />
For equality, we ignore all meta <strong>in</strong>formation:<br />
<strong>in</strong>stance GEq ′ f ⇒ GEq ′ (M1 t i f) where<br />
geq ′ (M1 x) (M1 y) = geq ′ x y<br />
All meta <strong>in</strong>formation is grouped under a s<strong>in</strong>gle datatype, so that<br />
we can easily ignore it all if we want to.<br />
Functions such as show and read can be implemented<br />
generically by access<strong>in</strong>g meta <strong>in</strong>formation.<br />
.<br />
.Well-Typed
Constructor classes<br />
To cover classes such as Functor , Traversable , Foldable<br />
generically, we need a way to map between a type constructor<br />
and its representation:<br />
class <strong>Generic</strong>1 f where<br />
type Rep1 f :: ∗ → ∗<br />
from1 :: f a → Rep1 f a<br />
to1 :: Rep1 f a → f a<br />
Use the same representation type constructors, plus<br />
data Par1 p = Par1 {unPar1 :: p }<br />
data Rec1 f p = Rec1 {unRec1 :: f p}<br />
GHC from version 7.6 is able to derive <strong>Generic</strong>1 , too.<br />
.<br />
.Well-Typed
Conclusions<br />
◮ For more examples, look at generic-deriv<strong>in</strong>g.<br />
◮ As a user <strong>of</strong> libraries, less boilerplate, easy to use.<br />
◮ Safer (but less powerful) than Template <strong>Haskell</strong>.<br />
◮ As a library author: consider us<strong>in</strong>g this!<br />
.<br />
.Well-Typed
Thank you – Questions?
Extra slides
Template <strong>Haskell</strong><br />
◮ Has the full syntax tree. Can do much more.<br />
◮ You have to do more work to derive us<strong>in</strong>g TH.<br />
◮ It’s trickier to get it right. Corner cases. Name<br />
manipulation.<br />
◮ <strong>Datatype</strong>-generic functions are type-checked.<br />
◮ Uniform <strong>in</strong>terface to the user.<br />
◮ Admittedly, allow<strong>in</strong>g deriv<strong>in</strong>g would be even easier.<br />
.<br />
.Well-Typed
SYB, uniplate, multiplate, regular, multirec<br />
◮ Similar ideas.<br />
◮ Need other representations.<br />
◮ Except for SYB, no direct GHC support.<br />
◮ But we can convert! (ICFP 2013 submission)<br />
.<br />
.Well-Typed