1 / 33

Scrap your boilerplate with class

Scrap your boilerplate with class. Ralf L ä mmel, Simon Peyton Jones Microsoft Research. The seductive dream: customisable generic programming. Define a function generically “gsize t = 1 + gsize of t’s children”

basile
Télécharger la présentation

Scrap your boilerplate with class

An Image/Link below is provided (as is) to download presentation Download Policy: Content on the Website is provided to you AS IS for your information and personal use and may not be sold / licensed / shared on other websites without getting consent from its author. Content is provided to you AS IS for your information and personal use only. Download presentation by click this link. While downloading, if for some reason you are not able to download a presentation, the publisher may have deleted the file from their server. During download, if you can't get a presentation, the file might be deleted by the publisher.

E N D

Presentation Transcript


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

  2. The seductive dream: customisable generic programming • Define a function generically“gsize t = 1 + gsize of t’s children” • Override the generic defn at specific types“gsize of a string is the length of the string” • Use the generic function at any type“gsize <complicated data structure>”

  3. 1. Generic definition [TLDI’03] gsize :: Data a => a -> Int gsize t = 1 + sum (gmapQ gsize t) class Dataa 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 • NB: Cool higher rank type for gmapQ

  4. Need Data instance for each type (once and for all) • Higher rank type class Dataa 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 wheregmapQ f i = [] instance Data a => Data [a] wheregmapQ f [] = []gmapQ f (x:xs) = [f x, f xs]

  5. The seductive dream: customisable generic programming • Define a function generically“gsize t = 1 + gsize of t’s children” • Override the generic defn at specific types“gsize of a string is the length of the string” • Use the generic function at any type“gsize <complicated data structure>” 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 • Define a function generically“gsize t = 1 + gsize of t’s children” • Override the generic defn at specific types“gsize of a string is the length of the string” • Use the generic function at any type“gsize <complicated data structure>” Done! 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 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

  11. The seductive dream: customisable generic programming • Define a function generically“gsize t = 1 + gsize of t’s children” • Override the generic defn at specific types“gsize of a string is the length of the string” • Use the generic function at any type“gsize <complicated data structure>” Undone! Done better!

  12. Writing the generic code Why can’t 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

  14. Idea (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, we’d have to add a new super-class to Data, ...which is defined in a library

  15. Main idea of the talk Much Better Idea Parameterise over the superclass: [Hughes 1999]Data 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 ‘*’

  16. Much 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

  17. The seductive dream: customisable generic programming • Define a function generically“gsize t = 1 + gsize of t’s children” • Override the generic defn at specific types“gsize of a string is the length of the string” • Use the generic function at any type“gsize <complicated data structure>” Done again! Done better!

  18. Story so far • We can write a generic program once class Size a wheregsize :: 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 • Type inference fails • Haskell doesn’t 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) ...but no way to know thatcxt = Size (Data cxt t) dictionary required... gmapQ :: Data cxt a => (forall b. Data cxt b => b -> r) -> a -> [r]

  21. Type 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]

  22. Type 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]

  23. Things I swept under the carpet Done!(albeit still tiresome) • Type inference fails • Haskell doesn’t have abstraction over type classes • Recursive dictionaries are needed

  24. Recursive dictionaries instance (Data cxt a, cxt[a]) => Data cxt [a] wheregmapQ f [] = [] gmapQ f (x:xs) = [f x, f xs] (I1) • 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 Size t => Size t wheregsize t = 1 + sum (gmapQ gsize t) (I2)

  25. Recursive dictionaries i1 :: (Data cxt a, cxt [a]) -> Data cxt [a] i2 :: Data Size t -> Size t i3 :: Data cxt Int • Need (Size [Int]) • Use (I2) to get it from (Data Size [Int]) • Use (I1) to get that from (Data Size Int, Size [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 Done! • Type inference fails • Haskell doesn’t have abstraction over type classes • Recursive dictionaries are needed Done!

  28. Encoding type-class abstraction Wanted (cxt::*->pred) 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

  29. Encoding type-class abstraction Wanted (Size::*->pred) 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

  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. SYB home page:http://www.cs.vu.nl/boilerplate/ 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)

  32. Recursive dictionaries Known instances Constraint to be solved Solve( S, C ) = Solve( S, D1 ) if S contains... instance (D1..Dn) => CSolve( S, Dn )

  33. Recursive 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 C’s sub-goals (cf Sulzmann05)

More Related