Presentation is loading. Please wait.

Presentation is loading. Please wait.

Scrap your boilerplate with class Ralf Lämmel, Simon Peyton Jones Microsoft Research.

Similar presentations


Presentation on theme: "Scrap your boilerplate with class Ralf Lämmel, Simon Peyton Jones Microsoft Research."— Presentation transcript:

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 )


Download ppt "Scrap your boilerplate with class Ralf Lämmel, Simon Peyton Jones Microsoft Research."

Similar presentations


Ads by Google