Title: Scrap your boilerplate with class
1Scrap your boilerplate with class
- Ralf Lämmel, Simon Peyton Jones
- Microsoft Research
2The seductive dream customisable generic
programming
- Define a function genericallygsize t 1
gsize of ts children - Override the generic defn at specific
typesgsize of a string is the length of the
string - Use the generic function at any typegsize
31. Generic definition TLDI03
gsize Data a a - Int gsize t 1 sum
(gmapQ gsize t) class Data a where gmapQ
(forall b. Data b b - r) - a - r--
(gmapQ f t) applies f to each of ts-- children,
returning list of results
- NB Cool higher rank type for gmapQ
4Need Data instance for each type (once and for
all)
class Data a where gmapQ (forall b. Data b
b - r) - a - r-- (gmapQ f t) applies
f to each of ts-- children, returning list of
results instance Data Int wheregmapQ f i
instance Data a Data a wheregmapQ f
gmapQ f (xxs) f x, f xs
5The seductive dream customisable generic
programming
- Define a function genericallygsize t 1
gsize of ts children - Override the generic defn at specific
typesgsize of a string is the length of the
string - Use the generic function at any typegsize
Done!
6Override gsize at specific type
- Plan A dynamic type test TLDI03
gsizeString Char - Int gsizeString s
length s gsize Data a a - Int gsize (\t
- 1 sum (gmapQ gsize t)) extQ
gsizeString
7The seductive dream customisable generic
programming
- Define a function genericallygsize t 1
gsize of ts children - Override the generic defn at specific
typesgsize of a string is the length of the
string - Use the generic function at any typegsize
Done!
Done!
8Not quite...
- Problems with Plan A
- Dynamic type test costs
- No static check for overlap
- Fiddly for type constructors ICFP04
- Worst of all tying the knot prevents further
extension
gsize Data a a - Int gsize t (1 sum
(gmapQ gsize t)) extQ gsizeString
9Tantalising Plan B type classes
class Size a where gsize a - Int instance
Size a Size a where gsize xs length xs
- Can add new types, with type-specific instances
for gsize, later - No dynamic type checks
- Plays nicely with type constructors
10...BUT
- Boilerplate instance required for each new type,
even if only the generic behaviour is wanted
data MyType a MT Int a instance Size a Size
(MyType a) where gsize (MT i x) 1 gsize i
gsize x data YourType a YT a a instance Size
a Size (YourType a) where gsize (YT i j) 1
gsize i gsize j
11The seductive dream customisable generic
programming
- Define a function genericallygsize t 1
gsize of ts children - Override the generic defn at specific
typesgsize of a string is the length of the
string - Use the generic function at any typegsize
Undone!
Done better!
12 Writing the generic code
- Why cant we combine the two approaches, like
this?
Generic case
class Size a where gsize a - Int instance
Data t Size t wheregsize t 1 sum (gmapQ
gsize t) instance Size a Size a where ...
More specific cases over-ride
13...utter failure
instance Data t Size t wheregsize t 1 sum
(gmapQ gsize t)
gmapQ Data a (forall b. Data b b -
r) - a - r gsize Size b b - Int
(gmapQ gsize t) will give a Data dictionary to
gsize...
...but alas gsize needs a Size dictionary
14Idea (bad)
Make a Data dictionary contain a Size dictionary
class Size a Data a wheregmapQ (forall b.
Data b b - r) - a - r
Now the instance works... but the idea is a
non-starter For every new generic function,
wed have to add a new super-class to
Data, ...which is defined in a library
15Much Better Idea
Main idea of the talk
Parameterise over the superclass Hughes
1999Data dictionary contains a cxt dictionary
class (cxt a) Data cxt a wheregmapQ
(forall b. Data cxt b b - r) - a -
r
- cxt has kind -pred
- just as
- a has kind
16Much Better Idea nearly works
instance Data Size t Size t wheregsize t 1
sum (gmapQ gsize t)
gmapQ Data cxt a (forall b. Data cxt b
b - r) - a - r gsize Size b b - Int
(gmapQ gsize t) will give a (Data Size t)
dictionary to gsize...
...and gsize can get the Size dictionary from
inside it
17The seductive dream customisable generic
programming
- Define a function genericallygsize t 1
gsize of ts children - Override the generic defn at specific
typesgsize of a string is the length of the
string - Use the generic function at any typegsize
Done again!
Done better!
18Story so far
- We can write a generic program once
class Size a wheregsize a - Int
instance Data Size t Size t where ...
data Wibble ... deriving( Data )
- Optionally, add type-specific behaviour
instance Size Wibble where ...
- In short, happiness regular Haskell type-class
overloading plus generic definition
19Things I swept under the carpet
- Type inference fails
- Haskell doesnt have abstraction over type
classes - Recursive dictionaries are needed
20 Type inference fails
(Data Size t) dictionary available...
instance Data Size t Size t wheregsize t 1
sum (gmapQ gsize t)
(Data cxt t) dictionary required...
...but no way to know that cxt Size
gmapQ Data cxt a (forall b. Data cxt b
b - r) - a - r
21Type inference fails
instance Data Size t Size t wheregsize t 1
sum (gmapQ gsize t)
We really want to specify that cxt should be
instantiated by Size, at this call site
gmapQ Data cxt a (forall b. Data cxt b
b - r) - a - r
22Type proxy value argument
instance Data Size t Size t wheregsize t 1
sum (gmapQ gsProxy gsize t)
data Proxy (cxt -pred) gsProxy Proxy
Size gsProxy error urk
Type-proxy argument
Type-proxy argument
gmapQ Data cxt a Proxy cxt - (forall b.
Data cxt b b - r) - a - r
23Things I swept under the carpet
Done! (albeit still tiresome)
- Type inference fails
- Haskell doesnt have abstraction over type
classes - Recursive dictionaries are needed
24Recursive dictionaries
instance (Data cxt a, cxt a) Data cxt a
wheregmapQ f gmapQ f (xxs) f x, f
xs
(I1)
instance Data Size t Size t wheregsize t 1
sum (gmapQ gsize t)
(I2)
- Need (Size Int)
- Use (I2) to get it from (Data Size Int)
- Use (I1) to get that from (Data Size Int, Size
Int)
25Recursive dictionaries
i1 (Data cxt a, cxt a) - Data cxt a i2
Data Size t - Size t i3 Data cxt Int
rec d1Size Int i2 d2 d2Data Size
Int i1 (d3,d1) d3Data Size Int i3
- Need (Size Int)
- Use (I2) to get it from (Data Size Int)
- Use (I1) to get that from (Data Size Int, Size
Int)
26Recursive dictionaries
- Recursive dictionaries arise naturally from
solving constraints co-inductively - Coinduction to solve C, assume C, and then prove
Cs sub-goals - Sketch of details in paper formal details in
Sulzmann 2005
27Things I swept under the carpet
Done!
- Type inference fails
- Haskell doesnt have abstraction over type
classes - Recursive dictionaries are needed
Done!
28Encoding type-class abstraction
class (cxt a) Data cxt a wheregmapQ
(forall b. Data cxt b b - r) - a -
r
Encoding (cxt-)
class Sat (cxt a) Data cxt a wheregmapQ
(forall b. Data cxt b b - r) - a -
r class Sat a wheredict a
29Encoding type-class abstraction
instance Data Size t Size t wheregsize t 1
sum (gmapQ gsize t)
Encoding (SizeD-)
instance Data SizeD t Size t wheregsize t 1
sum (gmapQ (gsizeD dict) t) data SizeD a SD
(a - Int) gsizeD (SD gs) gs instance Size a
Sat (SizeD a) wheredict SD gsize
30Encoding type-class abstraction
- Details straightforward. Its a little fiddly,
but not hard - A very cool trick
- Does Haskell need native type-class abstraction?
31Summary
SYB home pagehttp//www.cs.vu.nl/boilerplate/
- A smooth way to combine generic functions with
the open extensibility of type-classes - No dynamic type tests, although they are still
available if you want them, via (Data Typeable
a) - Longer case study in paper
- Language extensions
- coinductive constraint solving (necessary)
- abstraction over type classes (convenient)
32Recursive dictionaries
Known instances
Constraint to be solved
- Solve( S, C )
- Solve( S, D1 ) if S contains... instance
(D1..Dn) CSolve( S, Dn )
33Recursive dictionaries
Known instances
Constraint to be solved
- Solve( S, C )
- Solve( S ? C, D1 ) if S contains... instance
(D1..Dn) CSolve( S ? C, Dn )
Coinduction to solve C, assume C, and then
prove Cs sub-goals (cf Sulzmann05)