scrap your boilerplate with class ralf lämmel, simon peyton jones microsoft research
Post on 21-Dec-2015
214 views
TRANSCRIPT
![Page 1: Scrap your boilerplate with class Ralf Lämmel, Simon Peyton Jones Microsoft Research](https://reader031.vdocument.in/reader031/viewer/2022013011/56649d5e5503460f94a3d65a/html5/thumbnails/1.jpg)
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](https://reader031.vdocument.in/reader031/viewer/2022013011/56649d5e5503460f94a3d65a/html5/thumbnails/2.jpg)
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](https://reader031.vdocument.in/reader031/viewer/2022013011/56649d5e5503460f94a3d65a/html5/thumbnails/3.jpg)
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](https://reader031.vdocument.in/reader031/viewer/2022013011/56649d5e5503460f94a3d65a/html5/thumbnails/4.jpg)
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](https://reader031.vdocument.in/reader031/viewer/2022013011/56649d5e5503460f94a3d65a/html5/thumbnails/5.jpg)
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](https://reader031.vdocument.in/reader031/viewer/2022013011/56649d5e5503460f94a3d65a/html5/thumbnails/6.jpg)
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](https://reader031.vdocument.in/reader031/viewer/2022013011/56649d5e5503460f94a3d65a/html5/thumbnails/7.jpg)
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](https://reader031.vdocument.in/reader031/viewer/2022013011/56649d5e5503460f94a3d65a/html5/thumbnails/8.jpg)
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](https://reader031.vdocument.in/reader031/viewer/2022013011/56649d5e5503460f94a3d65a/html5/thumbnails/9.jpg)
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](https://reader031.vdocument.in/reader031/viewer/2022013011/56649d5e5503460f94a3d65a/html5/thumbnails/10.jpg)
...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](https://reader031.vdocument.in/reader031/viewer/2022013011/56649d5e5503460f94a3d65a/html5/thumbnails/11.jpg)
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](https://reader031.vdocument.in/reader031/viewer/2022013011/56649d5e5503460f94a3d65a/html5/thumbnails/12.jpg)
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](https://reader031.vdocument.in/reader031/viewer/2022013011/56649d5e5503460f94a3d65a/html5/thumbnails/13.jpg)
...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](https://reader031.vdocument.in/reader031/viewer/2022013011/56649d5e5503460f94a3d65a/html5/thumbnails/14.jpg)
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](https://reader031.vdocument.in/reader031/viewer/2022013011/56649d5e5503460f94a3d65a/html5/thumbnails/15.jpg)
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](https://reader031.vdocument.in/reader031/viewer/2022013011/56649d5e5503460f94a3d65a/html5/thumbnails/16.jpg)
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](https://reader031.vdocument.in/reader031/viewer/2022013011/56649d5e5503460f94a3d65a/html5/thumbnails/17.jpg)
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](https://reader031.vdocument.in/reader031/viewer/2022013011/56649d5e5503460f94a3d65a/html5/thumbnails/18.jpg)
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](https://reader031.vdocument.in/reader031/viewer/2022013011/56649d5e5503460f94a3d65a/html5/thumbnails/19.jpg)
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](https://reader031.vdocument.in/reader031/viewer/2022013011/56649d5e5503460f94a3d65a/html5/thumbnails/20.jpg)
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](https://reader031.vdocument.in/reader031/viewer/2022013011/56649d5e5503460f94a3d65a/html5/thumbnails/21.jpg)
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](https://reader031.vdocument.in/reader031/viewer/2022013011/56649d5e5503460f94a3d65a/html5/thumbnails/22.jpg)
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](https://reader031.vdocument.in/reader031/viewer/2022013011/56649d5e5503460f94a3d65a/html5/thumbnails/23.jpg)
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](https://reader031.vdocument.in/reader031/viewer/2022013011/56649d5e5503460f94a3d65a/html5/thumbnails/24.jpg)
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](https://reader031.vdocument.in/reader031/viewer/2022013011/56649d5e5503460f94a3d65a/html5/thumbnails/25.jpg)
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](https://reader031.vdocument.in/reader031/viewer/2022013011/56649d5e5503460f94a3d65a/html5/thumbnails/26.jpg)
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](https://reader031.vdocument.in/reader031/viewer/2022013011/56649d5e5503460f94a3d65a/html5/thumbnails/27.jpg)
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](https://reader031.vdocument.in/reader031/viewer/2022013011/56649d5e5503460f94a3d65a/html5/thumbnails/28.jpg)
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](https://reader031.vdocument.in/reader031/viewer/2022013011/56649d5e5503460f94a3d65a/html5/thumbnails/29.jpg)
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](https://reader031.vdocument.in/reader031/viewer/2022013011/56649d5e5503460f94a3d65a/html5/thumbnails/30.jpg)
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](https://reader031.vdocument.in/reader031/viewer/2022013011/56649d5e5503460f94a3d65a/html5/thumbnails/31.jpg)
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](https://reader031.vdocument.in/reader031/viewer/2022013011/56649d5e5503460f94a3d65a/html5/thumbnails/32.jpg)
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](https://reader031.vdocument.in/reader031/viewer/2022013011/56649d5e5503460f94a3d65a/html5/thumbnails/33.jpg)
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 ))