Scrap your boilerplate with class
Download
1 / 33

Scrap your boilerplate with class - PowerPoint PPT Presentation


  • 98 Views
  • Uploaded on

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”

loader
I am the owner, or an agent authorized to act on behalf of the owner, of the copyrighted work described.
capcha
Download Presentation

PowerPoint Slideshow about 'Scrap your boilerplate with class' - aram


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.While downloading, if for some reason you are not able to download a presentation, the publisher may have deleted the file from their server.


- - - - - - - - - - - - - - - - - - - - - - - - - - E N D - - - - - - - - - - - - - - - - - - - - - - - - - -
Presentation Transcript
Scrap your boilerplate with class

Scrap your boilerplate with class

Ralf Lämmel, Simon Peyton Jones

Microsoft Research


The seductive dream customisable generic programming
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>”


1 generic definition tldi 03
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


Need data instance for each type once and for all
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]


The seductive dream customisable generic programming1
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!


Override gsize at specific type
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


The seductive dream customisable generic programming2
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!


Not quite
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


Tantalising plan b type classes
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


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


The seductive dream customisable generic programming3
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!


Writing the generic code
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


Utter failure
...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


Idea bad
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


Much better idea

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 ‘*’


Much better idea nearly works
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


The seductive dream customisable generic programming4
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!


Story so far
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


Things i swept under the carpet
Things I swept under the carpet

  • Type inference fails

  • Haskell doesn’t have abstraction over type classes

  • Recursive dictionaries are needed


Type inference fails
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]


Type inference fails1
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]


Type proxy value argument
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]


Things i swept under the carpet1
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


Recursive dictionaries
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)


Recursive dictionaries1
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


Recursive dictionaries2
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]


Things i swept under the carpet2
Things I swept under the carpet

Done!

  • Type inference fails

  • Haskell doesn’t have abstraction over type classes

  • Recursive dictionaries are needed

Done!


Encoding type class abstraction
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


Encoding type class abstraction1
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


Encoding type class abstraction2
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?


Summary

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)


Recursive dictionaries3
Recursive dictionaries

Known instances

Constraint to be solved

Solve( S, C )

= Solve( S, D1 ) if S contains... instance (D1..Dn) => CSolve( S, Dn )


Recursive dictionaries4
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)


ad