Download presentation
Presentation is loading. Please wait.
1
Scrap your boilerplate with class Ralf Lämmel, Simon Peyton Jones Microsoft Research
2
The seductive dream: customisable generic programming 1.Define a function generically “gsize t = 1 + gsize of t’s children” 2.Override the generic defn at specific types “gsize of a string is the length of the string” 3.Use the generic function at any type “gsize ”
3
1. Generic definition [TLDI’03] NB: Cool higher rank type for gmapQ 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 t’s -- children, returning list of results
4
Need Data instance for each type (once and for all) Higher rank type class Data a where gmapQ :: (forall b. Data b => b -> r) -> a -> [r] -- (gmapQ f t) applies f to each of t’s -- children, returning list of results instance Data Int where gmapQ f i = [] instance Data a => Data [a] where gmapQ f [] = [] gmapQ f (x:xs)= [f x, f xs]
5
The seductive dream: customisable generic programming 1.Define a function generically “gsize t = 1 + gsize of t’s children” 2.Override the generic defn at specific types “gsize of a string is the length of the string” 3.Use the generic function at any type “gsize ” Done!
6
Override gsize at specific type Plan A: dynamic type test [TLDI’03] gsizeString :: [Char] -> Int gsizeString s = length s gsize :: Data a => a -> Int gsize = (\t -> 1 + sum (gmapQ gsize t)) `extQ` gsizeString
7
The seductive dream: customisable generic programming 1.Define a function generically “gsize t = 1 + gsize of t’s children” 2.Override the generic defn at specific types “gsize of a string is the length of the string” 3.Use the generic function at any type “gsize ” Done!
8
Not quite... Problems with Plan A Dynamic type test costs No static check for overlap Fiddly for type constructors [ICFP’04] Worst of all: tying the knot prevents further extension gsize :: Data a => a -> Int gsize t = (1 + sum (gmapQ gsize t)) `extQ` gsizeString
9
Tantalising Plan B: type classes Can add new types, with type-specific instances for gsize, “later” No dynamic type checks Plays nicely with type constructors class Size a where gsize :: a -> Int instance Size a => Size [a] where gsize xs = length xs
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
11
The seductive dream: customisable generic programming 1.Define a function generically “gsize t = 1 + gsize of t’s children” 2.Override the generic defn at specific types “gsize of a string is the length of the string” 3.Use the generic function at any type “gsize ” Undone! Done better!
12
Writing the generic code Writing the generic code class Size a where gsize :: a -> Int instance Data t => Size t where gsize t = 1 + sum (gmapQ gsize t) instance Size a => Size [a] where... Generic case More specific cases over- ride Why can’t we combine the two approaches, like this?
13
...utter failure instance Data t => Size t where gsize 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
14
Idea (bad) Make a Data dictionary contain a Size dictionary class Size a => Data a where gmapQ :: (forall b. Data b => b -> r) -> a -> [r] Now the instance “works”... but the idea is a non-starter: For every new generic function, we’d have to add a new super-class to Data,...which is defined in a library
15
Much Better Idea Parameterise over the superclass: [Hughes 1999] Data dictionary contains a cxt dictionary class (cxt a) => Data cxt a where gmapQ :: (forall b. Data cxt b => b -> r) -> a -> [r] ‘ cxt ’ has kind ‘ *->pred ’ just as ‘ a ’ has kind ‘ * ’ Main idea of the talk
16
Much Better Idea [nearly] works instance Data Size t => Size t where gsize 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
17
The seductive dream: customisable generic programming 1.Define a function generically “gsize t = 1 + gsize of t’s children” 2.Override the generic defn at specific types “gsize of a string is the length of the string” 3.Use the generic function at any type “gsize ” Done again! Done better!
18
Story so far We can write a generic program once class Size a where gsize :: a -> Int instance Data Size t => Size t where... Later, define a new type data Wibble =... deriving( Data ) Optionally, add type-specific behaviour instance Size Wibble where... In short, happiness: regular Haskell type- class overloading plus generic definition
19
Things I swept under the carpet 1.Type inference fails 2.Haskell doesn’t have abstraction over type classes 3.Recursive dictionaries are needed
20
Type inference fails Type inference fails gmapQ :: Data cxt a => (forall b. Data cxt b => b -> r) -> a -> [r] instance Data Size t => Size t where gsize t = 1 + sum (gmapQ gsize t) (Data cxt t) dictionary required......but no way to know that cxt = Size (Data Size t) dictionary available...
21
Type inference fails gmapQ :: Data cxt a => (forall b. Data cxt b => b -> r) -> a -> [r] instance Data Size t => Size t where gsize t = 1 + sum (gmapQ gsize t) We really want to specify that cxt should be instantiated by Size, at this call site
22
Type proxy value argument gmapQ :: Data cxt a => Proxy cxt -> (forall b. Data cxt b => b -> r) -> a -> [r] instance Data Size t => Size t where gsize t = 1 + sum (gmapQ gsProxy gsize t) data Proxy (cxt :: *->pred) gsProxy :: Proxy Size gsProxy = error “urk” Type-proxy argument
23
Things I swept under the carpet 1.Type inference fails 2.Haskell doesn’t have abstraction over type classes 3.Recursive dictionaries are needed Done! (albeit still tiresome)
24
Recursive dictionaries Need (Size [Int]) Use (I2) to get it from (Data Size [Int]) Use (I1) to get that from (Data Size Int, Size [Int]) instance (Data cxt a, cxt [a]) => Data cxt [a] where gmapQ f [] = [] gmapQ f (x:xs) = [f x, f xs] instance Data Size t => Size t where gsize t = 1 + sum (gmapQ gsize t) (I2) (I1)
25
Recursive dictionaries Need (Size [Int]) Use (I2) to get it from (Data Size [Int]) Use (I1) to get that from (Data Size Int, Size [Int]) i1 :: (Data cxt a, cxt [a]) -> Data cxt [a] i2 :: Data Size t -> Size t i3 :: Data cxt Int rec d1::Size [Int] = i2 d2 d2::Data Size [Int]= i1 (d3,d1) d3::Data Size Int = i3
26
Recursive dictionaries Recursive dictionaries arise naturally from solving constraints co-inductively Coinduction: to solve C, assume C, and then prove C’s sub-goals Sketch of details in paper; formal details in [Sulzmann 2005]
27
Things I swept under the carpet 1.Type inference fails 2.Haskell doesn’t have abstraction over type classes 3.Recursive dictionaries are needed Done!
28
Encoding type-class abstraction Wanted ( cxt::*->pred ) class (cxt a) => Data cxt a where gmapQ :: (forall b. Data cxt b => b -> r) -> a -> [r] class Sat (cxt a) => Data cxt a where gmapQ :: (forall b. Data cxt b => b -> r) -> a -> [r] class Sat a where dict :: a Encoding ( cxt::*->* )
29
Encoding type-class abstraction Wanted ( Size::*->pred ) Encoding ( SizeD::*->* ) instance Data Size t => Size t where gsize t = 1 + sum (gmapQ gsize t) instance Data SizeD t => Size t where gsize t = 1 + sum (gmapQ (gsizeD dict) t) data SizeD a = SD (a -> Int) gsizeD (SD gs) = gs instance Size a => Sat (SizeD a) where dict = SD gsize
30
Encoding type-class abstraction Details straightforward. It’s a little fiddly, but not hard A very cool trick Does Haskell need native type-class abstraction?
31
Summary 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) SYB home page: http://www.cs.vu.nl/boilerplate/
32
Recursive dictionaries Solve( S, C ) = Solve( S, D 1 )if S contains...instance (D 1..D n ) => C Solve( S, D n ) Constraint to be solved Known instances
33
Recursive dictionaries Constraint to be solved Known instances Coinduction: to solve C, assume C, and then prove C’s sub-goals (cf Sulzmann05) Solve( S, C ) = Solve( S C, D 1 )if S contains...instance (D 1..D n ) => C Solve( S C, D n )
Similar presentations
© 2025 SlidePlayer.com. Inc.
All rights reserved.