Loading in 5 sec....

Scrap your boilerplate with classPowerPoint Presentation

Scrap your boilerplate with class

- By
**aram** - Follow User

- 98 Views
- Uploaded on

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

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]

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)

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

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

- 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

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

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

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)

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

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

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

- 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

- Type inference fails
- Haskell doesn’t have abstraction over type classes
- Recursive dictionaries are needed

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

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 carpet

Done!(albeit still tiresome)

- Type inference fails
- Haskell doesn’t have abstraction over type classes
- Recursive dictionaries are needed

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

Done!

- Type inference fails
- Haskell doesn’t have abstraction over type classes
- Recursive dictionaries are needed

Done!

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

- Details straightforward. It’s a little fiddly, but not hard
- A very cool trick
- Does Haskell need native type-class abstraction?

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 dictionaries

Known instances

Constraint to be solved

Solve( S, C )

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

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)

Download Presentation

Connecting to Server..