design patterns (?) for control abstraction
DESCRIPTION
Design patterns (?) for control abstraction. What do parsers, -calculus reducers, and Prolog interpreters have in common?. What’s it all about?. - PowerPoint PPT PresentationTRANSCRIPT
![Page 1: Design patterns (?) for control abstraction](https://reader035.vdocuments.net/reader035/viewer/2022062323/56815a8b550346895dc801b8/html5/thumbnails/1.jpg)
Design patterns (?) for control abstraction
What do parsers, -calculus reducers, and Prolog interpreters have in
common?
![Page 2: Design patterns (?) for control abstraction](https://reader035.vdocuments.net/reader035/viewer/2022062323/56815a8b550346895dc801b8/html5/thumbnails/2.jpg)
What’s it all about?
• If you’ve been anywhere near functional programmers during the last decade, you’ll have heard a lot about parser combinators, monads, monadic parser combinators, domain-specific embedded languages (DSEL), ..
• There are a lot more details to these, but the common theme are libraries of control abstractions, built up from higher-order functions
• We’ll look at a few examples and useful ideas that are not quite as well-known as they could be
![Page 3: Design patterns (?) for control abstraction](https://reader035.vdocuments.net/reader035/viewer/2022062323/56815a8b550346895dc801b8/html5/thumbnails/3.jpg)
Control structures• Language designers and program verifiers are
used to thinking in terms of program calculi, focussing on essential structure, e.g., basic operations and their units:– Sequential composition of actions/no action– Alternative composition of choices/no choice– [Parallel composition of processes/no process][not for today]
• Concrete languages come with their own complex, built-in control structures (historical design)– can be mapped to and understood as combinations of
basic operations, but have grown into fixed forms which may not be a good match for the problem at hand
![Page 4: Design patterns (?) for control abstraction](https://reader035.vdocuments.net/reader035/viewer/2022062323/56815a8b550346895dc801b8/html5/thumbnails/4.jpg)
User-defined control structures• Languages in which control structures are first-
class objects (higher order functions/procedures) make it easy to “roll your own” control structures– OOP: modelling of real-world objects– FP: modelling of real-world control structures?
• Design freedom needs guidance – try to identify:– domain-specific control structures– general-purpose control structures (sequence,
alternative, parallels, recursion, ..)• Reversed mapping (purpose-built designs)
– build libraries of complex, domain-specific structures from basic, general-purpose control structures
![Page 5: Design patterns (?) for control abstraction](https://reader035.vdocuments.net/reader035/viewer/2022062323/56815a8b550346895dc801b8/html5/thumbnails/5.jpg)
“the” example: parser combinators
• Old idea (e.g., Wadler 1985): – assume an operation for each BNF construct (literals,
sequential/alternative composition,..)– define what each construct does in terms of parsing– translate your grammar into a program using these
constructs (almost literal translation) you’ve got a parser for the grammar!
Philip Wadler, “How to Replace Failure by a List of Successes”,FPLCA’85, Springer LNCS 201
![Page 6: Design patterns (?) for control abstraction](https://reader035.vdocuments.net/reader035/viewer/2022062323/56815a8b550346895dc801b8/html5/thumbnails/6.jpg)
“Old-style” parser combinatorslit x (x’:xs) | x==x’ = [(x,xs)]lit x _ = []
empty v xs = [(v,xs)]fail xs = []
alt p q xs = (p xs)++(q xs)seq f p q xs = [ (f v1 v2,xs2) | (v1,xs1) <- p xs , (v2,xs2) <- q xs1 ]
rep p = alt (seq (:) p (rep p)) (empty [])rep1 p = seq cons p (rep p)
alts ps = foldr alt fail psseqs ps = foldr (seq (:)) (empty []) pslits xs = seqs [ lit x | x<-xs ]
type Parser v = String -> [(v,String)]
![Page 7: Design patterns (?) for control abstraction](https://reader035.vdocuments.net/reader035/viewer/2022062323/56815a8b550346895dc801b8/html5/thumbnails/7.jpg)
“the” example, continued• A grammar/parser for arithmetic expressions:
expr = alts [number, seqs [lits “(”, expr, op, expr, lits “)”]]number = rep1 digitop = alts [lits “+”, lits “-”, lits “*”, lits “/”]digit = alts [lits (show n) | n <- [0..9]]
• Useful observations: – only the literals really “do” any parsing – the
combinators form a coordination layer on top of that, organising the application of literal parsers to the input
– the parsing is domain-specific, the coordination is not• Modern variants tend to use monads for the
coordination layer (e.g., Hutton and Meijer 1996)
![Page 8: Design patterns (?) for control abstraction](https://reader035.vdocuments.net/reader035/viewer/2022062323/56815a8b550346895dc801b8/html5/thumbnails/8.jpg)
From Hugs’ ParseLib.hsnewtype Parser a = P {papply :: (String -> [(a,String)])}
instance Monad Parser where -- return :: a -> Parser a return v = P (\inp -> [(v,inp)])
-- >>= :: Parser a -> (a -> Parser b) -> Parser b (P p) >>= f = P (\inp -> concat [ papply (f v) out
| (v,out) <- p inp])
instance MonadPlus Parser where -- mzero :: Parser a mzero = P (\inp -> [])
-- mplus :: Parser a -> Parser a -> Parser a (P p) `mplus` (P q) = P (\inp -> (p inp ++ q inp))
![Page 9: Design patterns (?) for control abstraction](https://reader035.vdocuments.net/reader035/viewer/2022062323/56815a8b550346895dc801b8/html5/thumbnails/9.jpg)
From Hugs’ ParseLib.hsitem :: Parser Charitem = P (\inp -> case inp of [] -> [] (x:xs) -> [(x,xs)])
sat :: (Char -> Bool) -> Parser Charsat p = do { x <- item
; if p x then return x else mzero}
bracket :: Parser a -> Parser b -> Parser c -> Parser bbracket open p close =
do {open; x <- p; close; return x}…char, digit, letter, .., many, many1, sepby, sepby1,…
Item needs to inspect inp!
![Page 10: Design patterns (?) for control abstraction](https://reader035.vdocuments.net/reader035/viewer/2022062323/56815a8b550346895dc801b8/html5/thumbnails/10.jpg)
Grammar combinators?• We can write the coordination layer to be
independent of the particular task (parsing)– Then we can plug in different basic actions instead of
literal parsers, to get different grammar-like programs• Instead of just parser combinators, we get a
general form of control combinators, applicable to all tasks with grammar-like specifications– obvious examples: generating language strings,
unparsing (from AST to text), pretty-printing, …– Less obvious: syntax-directed editing, typing (?),
reduction strategies (think contexts and context-sensitive rules), automated reasoning strategies,…
![Page 11: Design patterns (?) for control abstraction](https://reader035.vdocuments.net/reader035/viewer/2022062323/56815a8b550346895dc801b8/html5/thumbnails/11.jpg)
That monad thing..(I)• Parsers transform Strings to produce ASTs,
unparsers transform ASTs to produce Strings, editors and reducers transform ASTs, ..– Generalise to state transformers
• Combinators for sequence, alternative, etc. are so common that we will use them often– Make them so general that one set of definitions works
for all applications? one size fits all?– Overload one set of combinators with application-
specific definitions? do the variants still have anything in common?
– A mixture of both: capture the commonalities, enable specialisation (a framework). Monad, MonadPlus
![Page 12: Design patterns (?) for control abstraction](https://reader035.vdocuments.net/reader035/viewer/2022062323/56815a8b550346895dc801b8/html5/thumbnails/12.jpg)
That monad thing..(II)• Type constructors:
– data [a] = [] | (a:[a])– data Maybe a = Nothing | Just a– data ST m s a = s -> m (a,s)
• Type constructor classes: for type constructor m,– an instance of Monad m defines sequential
composition (>>=) and its unit (return) – an instance of MonadPlus m defines alternative
composition (mplus) and its unit (mzero)(over things of type m a)
![Page 13: Design patterns (?) for control abstraction](https://reader035.vdocuments.net/reader035/viewer/2022062323/56815a8b550346895dc801b8/html5/thumbnails/13.jpg)
Monadclass Monad m where return :: a -> m a (>>=) :: m a -> (a -> m b) -> m b (>>) :: m a -> m b -> m b fail :: String -> m a -- Minimal complete definition: (>>=), return p >> q = p >>= \ _ -> q fail s = error s
-- some Instances of Monad
instance Monad Maybe where Just x >>= k = k x Nothing >>= k = Nothing return = Just fail s = Nothing
instance Monad [ ] where (x:xs) >>= f = f x ++ (xs >>= f) [] >>= f = [] return x = [x] fail s = []
![Page 14: Design patterns (?) for control abstraction](https://reader035.vdocuments.net/reader035/viewer/2022062323/56815a8b550346895dc801b8/html5/thumbnails/14.jpg)
MonadPlusclass Monad m => MonadPlus m where mzero :: m a mplus :: m a -> m a -> m a
-- some Instances of MonadPlus
instance MonadPlus Maybe where mzero = Nothing Nothing `mplus` ys = ys xs `mplus` ys = xs
instance MonadPlus [ ] where mzero = [] mplus = (++)
![Page 15: Design patterns (?) for control abstraction](https://reader035.vdocuments.net/reader035/viewer/2022062323/56815a8b550346895dc801b8/html5/thumbnails/15.jpg)
State transformer monadnewtype ST m s a = ST {unST :: s -> m (a,s)}
instance Monad m => Monad (ST m s) where -- return :: a -> ST m s a return v = ST (\inp -> return (v,inp))
-- >>= :: ST m s a -> (a -> ST m s b) -> ST m s b (ST p) >>= f = ST (\inp -> do {(v,out) <- p inp
; unST (f v) out })
instance MonadPlus m => MonadPlus (ST m s) where -- mzero :: ST m s a mzero = ST (\inp -> mzero)
-- mplus :: ST m s a -> ST m s a -> ST m s a (ST p) `mplus` (ST q) = ST (\inp -> (p inp `mplus` q inp))
![Page 16: Design patterns (?) for control abstraction](https://reader035.vdocuments.net/reader035/viewer/2022062323/56815a8b550346895dc801b8/html5/thumbnails/16.jpg)
State transformer monadnewtype ST m s a = ST {unST :: s -> m (a,s)}
instance Monad m => Monad (ST m s) where -- return :: a -> ST m s a return v = ST (\inp -> return (v,inp))
-- >>= :: ST m s a -> (a -> ST m s b) -> ST m s b (ST p) >>= f = ST (\inp -> do {(v,out) <- p inp
; unST (f v) out })
instance MonadPlus m => MonadPlus (ST m s) where -- mzero :: ST m s a mzero = ST (\inp -> mzero)
-- mplus :: ST m s a -> ST m s a -> ST m s a (ST p) `mplus` (ST q) = ST (\inp -> (p inp `mplus` q inp))
![Page 17: Design patterns (?) for control abstraction](https://reader035.vdocuments.net/reader035/viewer/2022062323/56815a8b550346895dc801b8/html5/thumbnails/17.jpg)
State transformer monadnewtype ST m s a = ST {unST :: s -> m (a,s)}
instance Monad m => Monad (ST m s) where -- return :: a -> ST m s a return v = ST (\inp -> return (v,inp))
-- >>= :: ST m s a -> (a -> ST m s b) -> ST m s b (ST p) >>= f = ST (\inp -> do {(v,out) <- p inp
; unST (f v) out })
instance MonadPlus m => MonadPlus (ST m s) where -- mzero :: ST m s a mzero = ST (\inp -> mzero)
-- mplus :: ST m s a -> ST m s a -> ST m s a (ST p) `mplus` (ST q) = ST (\inp -> (p inp `mplus` q inp))
![Page 18: Design patterns (?) for control abstraction](https://reader035.vdocuments.net/reader035/viewer/2022062323/56815a8b550346895dc801b8/html5/thumbnails/18.jpg)
Parsing, again-- newtype ST m s a = ST {unST :: s -> m (a,s)}
type Parser a = ST [] String a
-- combinators for free
-- basic parsers still neededlitP :: (Char -> Bool) -> Parser CharlitP p = ST (\inp -> case dropWhile isSpace inp of { (x:xs) | p x -> [(x,xs)] ; otherwise -> [] })
lit :: Char -> Parser Charlit c = litP (==c)
-- as well as auxiliary combinations…
![Page 19: Design patterns (?) for control abstraction](https://reader035.vdocuments.net/reader035/viewer/2022062323/56815a8b550346895dc801b8/html5/thumbnails/19.jpg)
AS/Parser/Grammar for data Exp = Var String | App Exp Exp | Lam String Exp deriving Show
exp = var `mplus` app `mplus` abs
var = do { v <- litP isAlpha ; return $ Var [v] }
app = do { lit '(' ; e1 <- exp ; e2 <- exp ; lit ')' ; return $ App e1 e2 }
abs = do { lit '\\' ; Var v <- var ; lit '.' ; e <- exp ; return $ Lam v e }
![Page 20: Design patterns (?) for control abstraction](https://reader035.vdocuments.net/reader035/viewer/2022062323/56815a8b550346895dc801b8/html5/thumbnails/20.jpg)
What about semantics/reduction? -calculus reduction semantics:
– (v.M) N M[vN] {context-free reduction; meant to be valid in all contexts}
• refined by a reduction strategy (limited contexts):
– Cnor[ (v.M) N ] ,nor Cnor[ M[vN] ] {context-sensitive, normal-order reduction}
– Cnor[] [] | (Cnor[] <expr>) {reduction contexts; expressions with a hole }
![Page 21: Design patterns (?) for control abstraction](https://reader035.vdocuments.net/reader035/viewer/2022062323/56815a8b550346895dc801b8/html5/thumbnails/21.jpg)
Translation to Haskell -reduction in Haskell:
beta (App (Lam v m) n) =return $ substitute v n m
beta _ = fail "not a redex“
• Normal-order reduction strategy in Haskell:
norStep e@(App m n) = beta e `mplus` (norStep m >>= (\m'-> return (App m' n)))
norStep _ = fail "not an application”
nor e = (norStep e >>= nor) `mplus` (return e)
![Page 22: Design patterns (?) for control abstraction](https://reader035.vdocuments.net/reader035/viewer/2022062323/56815a8b550346895dc801b8/html5/thumbnails/22.jpg)
And now, for something completely ..
.. different?
Embedding Prolog in Haskell
![Page 23: Design patterns (?) for control abstraction](https://reader035.vdocuments.net/reader035/viewer/2022062323/56815a8b550346895dc801b8/html5/thumbnails/23.jpg)
Prolog, by example
programming in predicate logic: – define predicates via facts and rules– find solutions to queries about your "knowledge base":
app([],Y,Y).app([X | XS],Y,[X | ZS]):-app(XS,Y,ZS).
?- app(X,Y,[1,2]).X=[], Y=[1,2];X=[1], Y=[2];X=[1,2], Y=[];no
![Page 24: Design patterns (?) for control abstraction](https://reader035.vdocuments.net/reader035/viewer/2022062323/56815a8b550346895dc801b8/html5/thumbnails/24.jpg)
Prolog, by exampleWhere's the logic?
assume(Y:app([],Y,Y) true) (X,XS,Y,ZS: app([X|XS],Y,[X|ZS]) app(XS,Y,ZS) )
thenX,Y: app(X,Y,[1,2])
proofX=[] Y=[1,2] X=[1] Y=[2] X=[1,2] Y=[]
![Page 25: Design patterns (?) for control abstraction](https://reader035.vdocuments.net/reader035/viewer/2022062323/56815a8b550346895dc801b8/html5/thumbnails/25.jpg)
Prolog, de-sugaredClosed-world assumption and de-sugaring:
A,B,C:App(A,B,C) (A=[] B=C)X,XS,Y,ZS:(A=[X|XS] C=[X|ZS] app(XS,Y,ZS))
Need equivalence rather than implication, as well as explicit unification and existential quantification, but now we're ready to go
![Page 26: Design patterns (?) for control abstraction](https://reader035.vdocuments.net/reader035/viewer/2022062323/56815a8b550346895dc801b8/html5/thumbnails/26.jpg)
Prolog, embedded in HaskellEmbedding takes little more than a page of
code (mostly, unification). The rest is our good old friends, state transformer monads:
: Sequential compositiontrue : return () : Alternative compositionfalse : mzero : = (function definition)Predicates : substitution transformersUnification : explicit codev : fresh variables
![Page 27: Design patterns (?) for control abstraction](https://reader035.vdocuments.net/reader035/viewer/2022062323/56815a8b550346895dc801b8/html5/thumbnails/27.jpg)
Prolog, embedded in Haskellapp a b c = (do { a === Nil ; b === c }) +++ (exists "" $ \x-> exists "" $ \xs-> exists "" $ \zs-> do { a === (x:::xs) ; c === (x:::zs) ; app xs b zs })
x2 = exists "x" $ \x-> exists "y" $ \y-> app x y (Atom "1":::Atom "2":::Nil)
Prolog> solve x2y_1=1:::2:::[] x_0=[]y_1=2:::[] x_0=1:::[]y_1=[] x_0=1:::2:::[]
![Page 28: Design patterns (?) for control abstraction](https://reader035.vdocuments.net/reader035/viewer/2022062323/56815a8b550346895dc801b8/html5/thumbnails/28.jpg)
-- importsdata Term = Var String | Atom String | Nil | Term:::Termtype Subst = [(String,Term)] ..showSubst s = ..simplify s = ..
data State = State { subst :: Subst , free :: Integer }type Predicate = ST [] State
fresh :: String -> Predicate Termfresh n = ST $ \s-> return (Var (n++"_"++show (free s)) ,s{free=free s+1})-- unification: substitution transformer(===) :: Term -> Term -> Predicate ()
true,false :: Predicate ()true = return ()false = mzero
exists :: String -> (String -> Predicate a) -> Predicate a exists n p = do { v <- fresh n ; p v }
solve x = mapM_ (putStrLn.showSubst) [ subst $ simplify s | (_,s) <- unST x (State [] 0) ]
![Page 29: Design patterns (?) for control abstraction](https://reader035.vdocuments.net/reader035/viewer/2022062323/56815a8b550346895dc801b8/html5/thumbnails/29.jpg)
-- examples
app a b c = (do { a === Nil ; b === c }) +++ (exists "" $ \x-> exists "" $ \xs-> exists "" $ \ys-> do { a === (x:::xs) ; c === (x:::ys) ; app xs b ys })
x0 = exists "x" $ \x-> app (Atom "1":::x) Nil (Atom "1":::Atom "2":::Nil)
x1 = exists "z" $ \z-> app (Atom "1":::Atom "2":::Nil) Nil z
x2 = exists "x" $ \x-> exists "y" $ \y-> app x y (Atom "1":::Atom "2":::Nil)
![Page 30: Design patterns (?) for control abstraction](https://reader035.vdocuments.net/reader035/viewer/2022062323/56815a8b550346895dc801b8/html5/thumbnails/30.jpg)
Summary• Parser combinators are only one of many
examples of a programming pattern with a grammar-like coordination language (Wadler'85 already suggested tacticals as another example; there has been some recent work on rewriting strategy combinators, e.g., for compiler optimisations and other program transformations)
• Monad,MonadPlus,state transformers, do notation facilitate reuse in this pattern
• Both transformer/containers and plain containers (Maybe,[],Trees, ..) fit the pattern
• Coordination and computation can be defined separately to enhance modularity