210 likes | 359 Views
Scrap Your Boilerplate. http://research.microsoft.com/~simonpj/papers/hmap/. Outline. Boilerplate code Idea Recursively apply Generic transform “ Casting ” solution AOP solution Visitor pattern Queries and monadic transformations Boilerplate 2 & 3. Boilerplate. Boilerplate Code.
E N D
Scrap Your Boilerplate http://research.microsoft.com/~simonpj/papers/hmap/
Outline • Boilerplate code • Idea • Recursively apply • Generic transform • “Casting” solution • AOP solution • Visitor pattern • Queries and monadic transformations • Boilerplate 2 & 3
Boilerplate Boilerplate Code data 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))
Idea • Apply a generic transform • Replacing incD, incU, incE and incS • Apply the transform • Recursively • Blindly • Only affect the items we want • Increase k = everywhere $ mkT $ incS k Recursively, blindly apply Generate a generic transform
Recursively apply • One-layer traversal • class Typeable a => Term a where gmapT :: (forall b. Term b => b -> b) -> a -> a • instance Term Employee where gmapT f (E per sal) = E (f per) (f sal) • instance Term Bool where gmapT f x = x • instance Term a => Term [a] where gmapT f [] = [] gmapT f (x:xs) = f x : f xs
Recursively apply (cont.) • everywhere :: Term a => (forall b. Term b => b -> b) -> a -> a • everywhere f x = f $ gmapT (everywhere f) x • everywhere’ f x = gmapT (everywhere’ f) (f x)
Recursively apply (cont.) • Every involved data type have to be an instance of Term • The instantiation can be generated automatically • “deriving” in GHC 6.4 • Different combinations of gmapT lead to different traversal policy • Rank-2 types, signature always needed
“Casting” Solution • class Typeable • cast :: (Typeable a, Typeable b) => a -> Maybe b • (cast ‘a’) :: Maybe Char • Just ‘a’ • (cast ‘a’) :: Maybe Bool • Nothing • (cast True) :: Maybe Bool • Just True
“Casting” Solution (cont.) • mkT: generate a generic transform • When the applied parameter is what we expect, apply the transform • Otherwise, work like id • mkT :: (Typeable a, Typeable b) => (b -> b) -> a -> a • mkT f = case cast f of Just g -> g Nothing -> id
“Casting” Solution (cont.) • Need a language supported “cast” • GHC 6.4 internal/library • Data.Typeable: Typeable, cast • Data.Generics.Basic: Data (Term) • Cannot write transformation directly, always generated by mkT
AOP Solution • incS is itself the (dummy) generic transform • Decide whether the transform is applied or not by advice • incS :: Float -> a -> aincS k = id • advice around {incS k} (arg::Salary) = case arg of (S x) -> (S x*(1+k)) • increase k = everywhere $ incS k
AOP Solution (cont.) • type Salary = Float • In “casting” solution, every field with type Float will be multiplied (including height, weight, etc.) • In AOP Solution (?):advice around {incS k} (arg::Salary) = arg * (1 + k)
AOP Solution (cont.) • Less boilerplate code • Less data type for distinguishing meaning of data • Dummy transformation, real work is done by advices • idT = idadvice around {idT} (arg::Salary) = incS k argincrease = everywhere idT
Visitor pattern (cont.) • Widely used in single-dispatch OO languages • Visited object do the recursion • Decide whether the transform is applied or not by dispatching
Queries and monadic transform • class Typeable a => Term a where gmapT :: (forall b. Term b => b -> b) -> a -> a gmapQ :: (forall b. Term b=> b -> r) -> a -> [r] gmapM :: Monad m => (forall b. Term b -> m a) -> a -> m a-- special cases of gfoldl • everything :: Term a => (r -> r -> r) -> (forall a. Term a => a -> r) -> a -> r • everywhereM :: (b -> m b) -> a -> m a
Queries and monadic transform (cont.) • (r `mkQ` q) a = case cast a of Just b -> q b Nothing -> r • mkM f = case cast f of Just g -> g Nothing -> return • totalBill = everything (+) (0 `mkQ` billS) • dbSalaries = everywhereM (mkM lookupE)
Queries and monadic transform (cont.) • queryS _ = 0 :: Float • advice around {queryS} (arg::Salary) = case arg of S s -> s • totalBill = everything (+) queryS • transM _ = return • advice around {transM} (arg::Employee) = case arg of E p@(P n _) _ -> do s <- dbLookup n; return $ E p s • dbSalaries = everywhereM transM
Boilerplate 2 • Reflection: Already in GHC 6.4 library • Generic map, zip • extQ, extT, extM • Extend a generic query/transformation/ monadic transformation by a type-specific case • GHC 6.4, Data.Generics.Alias
Boilerplate 3 • Generalizing queries on Data (Term) • gsize :: Data a => a -> Int • gsize t = gsize_def `extQ` name_size `extQ` phone_size • class Size a where gsize :: a -> Int • instance Data t => Size t where gsize t = 1 + sum (gmapQ gsize t) • class Size a => Data a where … Needed for “every” query This belongs to the SYB “library”
Boilerplate 3 (cont.) • Need “type variable over classes” • Can be encoded straightforwardly in standard Haskell • API of Data changed • AOP solution cleaner • gsize t = 1 + sum (gmapQ gsize t) • advice around {gsize} (arg::Name) = case arg of (N _) -> 1 • advice around {gsize} (arg::PhoneNumber) = length arg -- another special case