scrap your boilerplate with class ralf lämmel, simon peyton jones microsoft research

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

Post on 21-Dec-2015

215 views

Category:

Documents


0 download

TRANSCRIPT

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

Scrap your boilerplate Scrap your boilerplate with classwith class

Ralf LRalf Läämmel, Simon Peyton Jonesmmel, Simon Peyton Jones

Microsoft ResearchMicrosoft Research

Page 2: Scrap your boilerplate with class Ralf Lämmel, Simon Peyton Jones Microsoft Research

The seductive dream: The seductive dream: customisable generic programmingcustomisable generic programming

1.1. Define a function genericallyDefine a function generically“gsize t = 1 + gsize of t’s children”“gsize t = 1 + gsize of t’s children”

2.2. Override the generic defn at specific typesOverride the generic defn at specific types“gsize of a string is the length of the string”“gsize of a string is the length of the string”

3.3. Use the generic function at any typeUse the generic function at any type“gsize <complicated data structure>”“gsize <complicated data structure>”

Page 3: Scrap your boilerplate with class Ralf Lämmel, Simon Peyton Jones Microsoft Research

1. Generic definition 1. Generic definition [TLDI’03][TLDI’03]

NB: Cool higher rank type for gmapQNB: 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

Page 4: Scrap your boilerplate with class Ralf Lämmel, Simon Peyton Jones Microsoft Research

Need Data instance for each type Need Data instance for each type (once and for all)(once and for all)

Higher rank typeHigher 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 wheregmapQ f i = []

instance Data a => Data [a] wheregmapQ f [] = []gmapQ f (x:xs) = [f x, f xs]

Page 5: Scrap your boilerplate with class Ralf Lämmel, Simon Peyton Jones Microsoft Research

The seductive dream: The seductive dream: customisable generic programmingcustomisable generic programming

1.1. Define a function genericallyDefine a function generically“gsize t = 1 + gsize of t’s children”“gsize t = 1 + gsize of t’s children”

2.2. Override the generic defn at specific typesOverride the generic defn at specific types“gsize of a string is the length of the string”“gsize of a string is the length of the string”

3.3. Use the generic function at any typeUse the generic function at any type“gsize <complicated data structure>”“gsize <complicated data structure>”

Done!

Page 6: Scrap your boilerplate with class Ralf Lämmel, Simon Peyton Jones Microsoft Research

Override gsize at specific typeOverride gsize at specific type

Plan A: dynamic type test [TLDI’03]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

Page 7: Scrap your boilerplate with class Ralf Lämmel, Simon Peyton Jones Microsoft Research

The seductive dream: The seductive dream: customisable generic programmingcustomisable generic programming

1.1. Define a function genericallyDefine a function generically“gsize t = 1 + gsize of t’s children”“gsize t = 1 + gsize of t’s children”

2.2. Override the generic defn at specific typesOverride the generic defn at specific types“gsize of a string is the length of the string”“gsize of a string is the length of the string”

3.3. Use the generic function at any typeUse the generic function at any type“gsize <complicated data structure>”“gsize <complicated data structure>”

Done!

Done!

Page 8: Scrap your boilerplate with class Ralf Lämmel, Simon Peyton Jones Microsoft Research

Not quite...Not quite...

Problems with Plan AProblems with Plan ADynamic type test costsDynamic type test costs

No static check for overlapNo static check for overlap

Fiddly for type constructors [ICFP’04]Fiddly for type constructors [ICFP’04]

Worst of all: tying the knot Worst of all: tying the knot prevents further extensionprevents further extension

gsize :: Data a => a -> Int

gsize t = (1 + sum (gmapQ gsize t))

`extQ`

gsizeString

Page 9: Scrap your boilerplate with class Ralf Lämmel, Simon Peyton Jones Microsoft Research

Tantalising Plan B: type classesTantalising Plan B: type classes

Can add new types, with type-specific Can add new types, with type-specific instances for gsize, “later”instances for gsize, “later”

No dynamic type checksNo dynamic type checks

Plays nicely with type constructorsPlays nicely with type constructors

class Size a where gsize :: a -> Int

instance Size a => Size [a] where gsize xs = length xs

Page 10: Scrap your boilerplate with class Ralf Lämmel, Simon Peyton Jones Microsoft Research

...BUT...BUTBoilerplate instance required for Boilerplate instance required for each new type, each new type, even if only the even if only the generic behaviour is wantedgeneric 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

Page 11: Scrap your boilerplate with class Ralf Lämmel, Simon Peyton Jones Microsoft Research

The seductive dream: The seductive dream: customisable generic programmingcustomisable generic programming

1.1. Define a function genericallyDefine a function generically“gsize t = 1 + gsize of t’s children”“gsize t = 1 + gsize of t’s children”

2.2. Override the generic defn at specific typesOverride the generic defn at specific types“gsize of a string is the length of the string”“gsize of a string is the length of the string”

3.3. Use the generic function at any typeUse the generic function at any type“gsize <complicated data structure>”“gsize <complicated data structure>”

Undone!

Done better!

Page 12: Scrap your boilerplate with class Ralf Lämmel, Simon Peyton Jones Microsoft Research

Writing the generic codeWriting the generic code

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

Generic case

More specific cases

over-ride

Why can’t we combine the two Why can’t we combine the two approaches, like this?approaches, like this?

Page 13: Scrap your boilerplate with class Ralf Lämmel, Simon Peyton Jones Microsoft Research

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

Page 14: Scrap your boilerplate with class Ralf Lämmel, Simon Peyton Jones Microsoft Research

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

Page 15: Scrap your boilerplate with class Ralf Lämmel, Simon Peyton Jones Microsoft Research

Much Better IdeaMuch Better IdeaParameterise 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 ‘*’

Main idea of the talk

Page 16: Scrap your boilerplate with class Ralf Lämmel, Simon Peyton Jones Microsoft Research

Much Better Idea [nearly] worksMuch 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

Page 17: Scrap your boilerplate with class Ralf Lämmel, Simon Peyton Jones Microsoft Research

The seductive dream: The seductive dream: customisable generic programmingcustomisable generic programming

1.1. Define a function genericallyDefine a function generically“gsize t = 1 + gsize of t’s children”“gsize t = 1 + gsize of t’s children”

2.2. Override the generic defn at specific typesOverride the generic defn at specific types“gsize of a string is the length of the string”“gsize of a string is the length of the string”

3.3. Use the generic function at any typeUse the generic function at any type“gsize <complicated data structure>”“gsize <complicated data structure>”

Done again!

Done better!

Page 18: Scrap your boilerplate with class Ralf Lämmel, Simon Peyton Jones Microsoft Research

Story so farStory so farWe can write a generic program onceWe can write a generic program once

class Size a wheregsize :: a -> Int

instance Data Size t => Size t

where ...

Later, define a new typeLater, define a new typedata Wibble = ... deriving( Data )

OptionallyOptionally, add type-specific behaviour, add type-specific behaviour

instance Size Wibble where ...

In short, happiness: regular Haskell type-In short, happiness: regular Haskell type-class overloading class overloading plusplus generic definition generic definition

Page 19: Scrap your boilerplate with class Ralf Lämmel, Simon Peyton Jones Microsoft Research

Things I swept under the carpetThings I swept under the carpet1.1. Type inference failsType inference fails

2.2. Haskell doesn’t have abstraction over Haskell doesn’t have abstraction over type classestype classes

3.3. Recursive dictionaries are neededRecursive dictionaries are needed

Page 20: Scrap your boilerplate with class Ralf Lämmel, Simon Peyton Jones Microsoft Research

Type inference failsType inference fails

gmapQ :: Data cxt a => (forall b. Data cxt b => b ->

r)-> a -> [r]

instance Data Size t => Size t wheregsize t = 1 + sum (gmapQ gsize t)

(Data cxt t) dictionary required...

...but no way to know that

cxt = Size

(Data Size t) dictionary available...

Page 21: Scrap your boilerplate with class Ralf Lämmel, Simon Peyton Jones Microsoft Research

Type inference failsType inference fails

gmapQ :: Data cxt a => (forall b. Data cxt b => b ->

r)-> a -> [r]

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

Page 22: Scrap your boilerplate with class Ralf Lämmel, Simon Peyton Jones Microsoft Research

Type proxy value argumentType proxy value argument

gmapQ :: Data cxt a => Proxy cxt-> (forall b. Data cxt b => b ->

r)-> a -> [r]

instance Data Size t => Size t wheregsize t = 1 + sum (gmapQ gsProxy gsize t)

data Proxy (cxt :: *->pred)

gsProxy :: Proxy SizegsProxy = error “urk”

Type-proxy argument

Type-proxy argument

Page 23: Scrap your boilerplate with class Ralf Lämmel, Simon Peyton Jones Microsoft Research

Things I swept under the carpetThings I swept under the carpet1.1. Type inference failsType inference fails

2.2. Haskell doesn’t have abstraction over Haskell doesn’t have abstraction over type classestype classes

3.3. Recursive dictionaries are neededRecursive dictionaries are needed

Done!

(albeit still

tireso

me)

Page 24: Scrap your boilerplate with class Ralf Lämmel, Simon Peyton Jones Microsoft Research

Recursive dictionariesRecursive dictionaries

Need Need (Size [Int])(Size [Int])

Use (I2) to get it from Use (I2) to get it from (Data Size [Int])(Data Size [Int])

Use (I1) to get that from Use (I1) to get that from (Data Size Int, Size [Int])(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 wheregsize t = 1 + sum (gmapQ gsize t)

(I2)

(I1)

Page 25: Scrap your boilerplate with class Ralf Lämmel, Simon Peyton Jones Microsoft Research

Recursive dictionariesRecursive dictionaries

Need Need (Size [Int])(Size [Int])

Use (I2) to get it from Use (I2) to get it from (Data Size [Int])(Data Size [Int])

Use (I1) to get that from Use (I1) to get that from (Data Size Int, Size [Int])(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 d2d2::Data Size [Int] = i1

(d3,d1)d3::Data Size Int = i3

Page 26: Scrap your boilerplate with class Ralf Lämmel, Simon Peyton Jones Microsoft Research

Recursive dictionariesRecursive dictionariesRecursive dictionaries arise naturally from Recursive dictionaries arise naturally from solving constraints solving constraints co-inductivelyco-inductively

Coinduction: to solve C, assume C, and then prove C’s sub-goals

Sketch of details in paper; formal details in [Sulzmann 2005]

Page 27: Scrap your boilerplate with class Ralf Lämmel, Simon Peyton Jones Microsoft Research

Things I swept under the carpetThings I swept under the carpet1.1. Type inference failsType inference fails

2.2. Haskell doesn’t have abstraction over Haskell doesn’t have abstraction over type classestype classes

3.3. Recursive dictionaries are neededRecursive dictionaries are needed

Done!

Done!

Page 28: Scrap your boilerplate with class Ralf Lämmel, Simon Peyton Jones Microsoft Research

Encoding type-class abstractionEncoding type-class abstractionWanted Wanted ((cxt::*->predcxt::*->pred))class (cxt a) => Data cxt a wheregmapQ :: (forall b. Data cxt b => b -> r) -> a -> [r]

class Sat (cxt a) => Data cxt a wheregmapQ :: (forall b. Data cxt b => b -> r) -> a -> [r]

class Sat a wheredict :: a

EncodingEncoding ((cxt::*->*cxt::*->*))

Page 29: Scrap your boilerplate with class Ralf Lämmel, Simon Peyton Jones Microsoft Research

Encoding type-class abstractionEncoding type-class abstractionWanted Wanted ((Size::*->predSize::*->pred))

EncodingEncoding ((SizeD::*->*SizeD::*->*))

instance Data Size t => Size t wheregsize t = 1 + sum (gmapQ gsize t)

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

Page 30: Scrap your boilerplate with class Ralf Lämmel, Simon Peyton Jones Microsoft Research

Encoding type-class abstractionEncoding type-class abstraction

Details straightforward. It’s a little fiddly, Details straightforward. It’s a little fiddly, but not hardbut not hard

A very cool trickA very cool trick

Does Haskell need native type-class Does Haskell need native type-class abstraction?abstraction?

Page 31: Scrap your boilerplate with class Ralf Lämmel, Simon Peyton Jones Microsoft Research

SummarySummaryA smooth way to combine generic functions A smooth way to combine generic functions with the open extensibility of type-classeswith the open extensibility of type-classes

No dynamic type tests, although they are still No dynamic type tests, although they are still available if you want them, via available if you want them, via

(Data Typeable a)(Data Typeable a)

Longer case study in paperLonger case study in paper

Language extensions: Language extensions: coinductive constraint solving (necessary)coinductive constraint solving (necessary)

abstraction over type classes (convenient)abstraction over type classes (convenient)

SYB home page:SYB home page:http://www.cs.vu.nl/boilerplate/http://www.cs.vu.nl/boilerplate/

Page 32: Scrap your boilerplate with class Ralf Lämmel, Simon Peyton Jones Microsoft Research

Recursive dictionariesRecursive dictionaries

Solve( S, C )Solve( S, C )

= Solve( S, D= Solve( S, D1 1 )) if S containsif S contains

...... instance (Dinstance (D11..D..Dnn) => C) => C

Solve( S, DSolve( S, Dn n ))

Constraint to be solved

Known instances

Page 33: Scrap your boilerplate with class Ralf Lämmel, Simon Peyton Jones Microsoft Research

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

= Solve( S = Solve( S C C, D, D1 1 )) if S containsif S contains

...... instance (Dinstance (D11..D..Dnn) => C) => C

Solve( S Solve( S C C, D, Dn n ))