Scrap Your Boilerplate /~simonpj/papers/hmap

21
Scrap Your Scrap Your Boilerplate Boilerplate http://research.microsoft. http://research.microsoft. com/~simonpj/papers/hmap/ com/~simonpj/papers/hmap/

Transcript of Scrap Your Boilerplate /~simonpj/papers/hmap

Page 1: Scrap Your Boilerplate  /~simonpj/papers/hmap

Scrap Your BoilerplateScrap Your Boilerplate

http://research.microsoft.com/~http://research.microsoft.com/~simonpj/papers/hmap/simonpj/papers/hmap/

Page 2: Scrap Your Boilerplate  /~simonpj/papers/hmap

OutlineOutline

Boilerplate codeBoilerplate code IdeaIdea Recursively applyRecursively apply Generic transformGeneric transform

• ““Casting” solutionCasting” solution• AOP solutionAOP solution

Visitor patternVisitor pattern Queries and monadic transformationsQueries and monadic transformations Boilerplate 2 & 3Boilerplate 2 & 3

Page 3: Scrap Your Boilerplate  /~simonpj/papers/hmap

Boilerplate CodeBoilerplate Codedata Company = C [Dept]data Dept = D Name Manager [SubUnit]data SubUnit = PU Employee | DU Deptdata Employee = E Person Salarydata Person = P Name Addressdata Salary = S Floattype Manager = Employeetype Name = Stringtype Address = String

increase :: Float -> Company -> Companyincrease k (C ds) = C (map (incD k) ds)

incD k (D nm mgr us) = D nm (incE k mgr) (map (incU k) us)incU k (PU e) = PU (incE k e)incU k (DU d) = DU (incD k d)incE k (E p s) = E p (incS k s)incS k (S s) = S (s * (1 + k))

Boilerplate

Page 4: Scrap Your Boilerplate  /~simonpj/papers/hmap

IdeaIdea

Apply a generic transformApply a generic transform• Replacing incD, incU, incE and incSReplacing incD, incU, incE and incS

Apply the transformApply the transform• RecursivelyRecursively• BlindlyBlindly

Only affect the items we wantOnly affect the items we want Increase k =Increase k =

everywhere $ mkT $ incS k everywhere $ mkT $ incS kGenerate a generic

transformRecursively, blindly

apply

Page 5: Scrap Your Boilerplate  /~simonpj/papers/hmap

Recursively applyRecursively apply One-layer traversalOne-layer traversal class Typeable a => Term a whereclass Typeable a => Term a where

gmapT :: (forall b. Term b => b -> b) gmapT :: (forall b. Term b => b -> b) -> a -> a -> a -> a

instance Term Employee whereinstance Term Employee where gmapT f (E per sal) = E (f per) (f sal) gmapT f (E per sal) = E (f per) (f sal)

instance Term Bool whereinstance Term Bool where gmapT f x = x gmapT f x = x

instance Term a => Term [a] whereinstance Term a => Term [a] where gmapT f [] gmapT f [] = []= [] gmapT f (x:xs) gmapT f (x:xs) = f x : f xs= f x : f xs

Page 6: Scrap Your Boilerplate  /~simonpj/papers/hmap

Recursively apply (cont.)Recursively apply (cont.)

everywhere :: Term aeverywhere :: Term a => (forall b. Term b => b -> b) => (forall b. Term b => b -> b) -> a -> a -> a -> a

everywhere f x =everywhere f x = f $ gmapT (everywhere f) x f $ gmapT (everywhere f) x

everywhere’ f x =everywhere’ f x = gmapT (everywhere’ f) (f x) gmapT (everywhere’ f) (f x)

Page 7: Scrap Your Boilerplate  /~simonpj/papers/hmap

Recursively apply (cont.)Recursively apply (cont.)

Every involved data type have to be an iEvery involved data type have to be an instance of Termnstance of Term

The instantiation can be generated autoThe instantiation can be generated automaticallymatically• ““deriving” in GHC 6.4deriving” in GHC 6.4

Different combinations of gmapT lead to Different combinations of gmapT lead to different traversal policydifferent traversal policy

Rank-2 types, signature always neededRank-2 types, signature always needed

Page 8: Scrap Your Boilerplate  /~simonpj/papers/hmap

““Casting” SolutionCasting” Solution

class Typeableclass Typeable cast :: (Typeable a, Typeable b)cast :: (Typeable a, Typeable b)

=> a -> Maybe b => a -> Maybe b• (cast ‘a’) :: Maybe Char(cast ‘a’) :: Maybe Char

Just ‘a’Just ‘a’• (cast ‘a’) :: Maybe Bool(cast ‘a’) :: Maybe Bool

NothingNothing• (cast True) :: Maybe Bool(cast True) :: Maybe Bool

Just TrueJust True

Page 9: Scrap Your Boilerplate  /~simonpj/papers/hmap

““Casting” Solution (cont.)Casting” Solution (cont.)

mkT: generate a generic transformmkT: generate a generic transform• When the applied parameter is what we expWhen the applied parameter is what we exp

ect, apply the transformect, apply the transform• Otherwise, work like idOtherwise, work like id

mkT :: (Typeable a, Typeable b)mkT :: (Typeable a, Typeable b) => (b -> b) -> a -> a => (b -> b) -> a -> a

mkT f = case cast f ofmkT f = case cast f ofJust gJust g -> g-> gNothingNothing -> id-> id

Page 10: Scrap Your Boilerplate  /~simonpj/papers/hmap

““Casting” Solution (cont.)Casting” Solution (cont.)

Need a language supported “cast”Need a language supported “cast”• GHC 6.4 internal/libraryGHC 6.4 internal/library• Data.Typeable: Typeable, castData.Typeable: Typeable, cast• Data.Generics.Basic: Data (Term)Data.Generics.Basic: Data (Term)

Cannot write transformation directly, alCannot write transformation directly, always generated by mkTways generated by mkT

Page 11: Scrap Your Boilerplate  /~simonpj/papers/hmap

AOP SolutionAOP Solution

incS is itself the (dummy) generic transformincS is itself the (dummy) generic transform Decide whether the transform is applied or not Decide whether the transform is applied or not

by adviceby advice incS :: Float -> a -> aincS :: Float -> a -> a

incS k = idincS k = id advice around {incS k} (arg::Salary) =advice around {incS k} (arg::Salary) =

case arg of (S x) -> (S x*(1+k)) case arg of (S x) -> (S x*(1+k)) increase k = everywhere $ incS kincrease k = everywhere $ incS k

Page 12: Scrap Your Boilerplate  /~simonpj/papers/hmap

AOP Solution (cont.)AOP Solution (cont.)

type Salary = Floattype Salary = Float In “casting” solution, every field with tIn “casting” solution, every field with t

ype Float will be multiplied (including heype Float will be multiplied (including height, weight, etc.)ight, weight, etc.)

In AOP Solution (?):In AOP Solution (?):advice around {incS k} (arg::Salary)advice around {incS k} (arg::Salary) = arg * (1 + k) = arg * (1 + k)

Page 13: Scrap Your Boilerplate  /~simonpj/papers/hmap

AOP Solution (cont.)AOP Solution (cont.)

Less boilerplate codeLess boilerplate code Less data type for distinguishing meaninLess data type for distinguishing meanin

g of datag of data Dummy transformation, real work is donDummy transformation, real work is don

e by advicese by advices• idT = ididT = id

advice around {idT} (arg::Salary) =advice around {idT} (arg::Salary) = incS k arg incS k argincrease = everywhere idTincrease = everywhere idT

Page 14: Scrap Your Boilerplate  /~simonpj/papers/hmap

Visitor patternVisitor pattern

Page 15: Scrap Your Boilerplate  /~simonpj/papers/hmap

Visitor pattern (cont.)Visitor pattern (cont.)

Widely used in single-dispatch OO Widely used in single-dispatch OO languageslanguages

Visited object do the recursionVisited object do the recursion Decide whether the transform is Decide whether the transform is

applied or not by dispatchingapplied or not by dispatching

Page 16: Scrap Your Boilerplate  /~simonpj/papers/hmap

Queries and monadic transformQueries and monadic transform

class Typeable a => Term a whereclass Typeable a => Term a where gmapT :: (forall b. Term b => b -> b) gmapT :: (forall b. Term b => b -> b) -> a -> a -> a -> a gmapQ :: (forall b. Term b=> b -> r) gmapQ :: (forall b. Term b=> b -> r) -> a -> [r] -> a -> [r] gmapM :: Monad m => gmapM :: Monad m => (forall b. Term b -> m a) -> a -> m a (forall b. Term b -> m a) -> a -> m a-- special cases of gfoldl-- special cases of gfoldl

everything :: Term a => (r -> r -> r)everything :: Term a => (r -> r -> r) -> (forall a. Term a => a -> r) -> a -> r -> (forall a. Term a => a -> r) -> a -> r

everywhereM :: (b -> m b) -> a -> m aeverywhereM :: (b -> m b) -> a -> m a

Page 17: Scrap Your Boilerplate  /~simonpj/papers/hmap

Queries and monadic transform Queries and monadic transform (cont.)(cont.)

(r `mkQ` q) a = case cast a of(r `mkQ` q) a = case cast a ofJust bJust b -> q b-> q bNothingNothing -> r-> r

mkM f = case cast f ofmkM f = case cast f ofJust gJust g -> g-> gNothingNothing -> return-> return

totalBill = everything (+) (0 `mkQ` billS)totalBill = everything (+) (0 `mkQ` billS) dbSalaries = everywhereM (mkM lookupE)dbSalaries = everywhereM (mkM lookupE)

Page 18: Scrap Your Boilerplate  /~simonpj/papers/hmap

Queries and monadic transform Queries and monadic transform (cont.)(cont.)

queryS _ = 0 :: FloatqueryS _ = 0 :: Float advice around {queryS} (arg::Salary) = case arg advice around {queryS} (arg::Salary) = case arg

of S s -> sof S s -> s totalBill = everything (+) queryStotalBill = everything (+) queryS

transM _ = returntransM _ = return advice around {transM} (arg::Employee)advice around {transM} (arg::Employee)

= case arg of E p@(P n _) _ -> = case arg of E p@(P n _) _ -> do s <- dbLookup n; return $ E p s do s <- dbLookup n; return $ E p s

dbSalaries = everywhereM transMdbSalaries = everywhereM transM

Page 19: Scrap Your Boilerplate  /~simonpj/papers/hmap

Boilerplate 2Boilerplate 2

Reflection: Already in GHC 6.4 libraryReflection: Already in GHC 6.4 library Generic map, zipGeneric map, zip extQ, extT, extMextQ, extT, extM

• Extend a generic query/transformation/ moExtend a generic query/transformation/ monadic transformation by a type-specific casenadic transformation by a type-specific case

• GHC 6.4, Data.Generics.AliasGHC 6.4, Data.Generics.Alias

Page 20: Scrap Your Boilerplate  /~simonpj/papers/hmap

Boilerplate 3Boilerplate 3

Generalizing queries on Data (Term)Generalizing queries on Data (Term)• gsize :: Data a => a -> Intgsize :: Data a => a -> Int• gsize t = gsize_def `extQ` name_sizegsize t = gsize_def `extQ` name_size

`extQ` phone_size `extQ` phone_size• class Size a whereclass Size a where

gsize :: a -> Int gsize :: a -> Int instance Data t => Size t whereinstance Data t => Size t where

gsize t = 1 + sum (gmapQ gsize t) gsize t = 1 + sum (gmapQ gsize t) class Size a => Data a where …class Size a => Data a where …

This belongs to the SYB “library”Needed for “every” query

Page 21: Scrap Your Boilerplate  /~simonpj/papers/hmap

Boilerplate 3 (cont.)Boilerplate 3 (cont.)

Need “type variable over classes”Need “type variable over classes”• Can be encoded straightforwardly in standard HaskCan be encoded straightforwardly in standard Hask

ellell API of Data changedAPI of Data changed AOP solution cleanerAOP solution cleaner

• gsize t = 1 + sum (gmapQ gsize t)gsize t = 1 + sum (gmapQ gsize t)• advice around {gsize} (arg::Name) =advice around {gsize} (arg::Name) =

case arg of (N _) -> 1 case arg of (N _) -> 1• advice around {gsize} (arg::PhoneNumber) =advice around {gsize} (arg::PhoneNumber) =

length arg -- another special case length arg -- another special case