Scrap your boilerplate with class Ralf Lämmel, Simon Peyton Jones Microsoft Research.
-
date post
21-Dec-2015 -
Category
Documents
-
view
214 -
download
0
Transcript of 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
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>”
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
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]
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!
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
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!
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
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
...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
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!
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?
...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 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
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
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!
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
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
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...
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
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
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)
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)
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
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]
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!
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::*->*))
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
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?
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/
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
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 ))