CS 541 meeting -*- Outline -*- * techniques for the implementation of domain specific languages some of the following ideas (and most of the code snippets) are from "Domain Specific Embedded Compilers" by Daan Leijen and Eric Meijer, Second Conference on Domain-Specific Languages, Austin Texas, U.S.A., Oct. 3-5, 1999 (Usenix, 1999), pages 109-122. ** syntax work from examples *** syntax matters if appropriate, overload Haskell operators such as + to do this, define an instance of the appropriate type class, such as Num for + make syntax uniform (regular) by defining the same operators on different kinds of data structures, using type classes for example, frequency a word occurrence in a sentence, paragraph, document for example, the various liftings in Fran to get the precedence of your infix operators correct, use infix declarations in Haskell. these are infix, infixl, infixr, infixl 9 ^ -- highest, most tight binding, left assoc. infixr 0 .===. -- lowest, least tight binding, right assoc. *** syntactic sugars use syntactic sugars to give the user better syntax and to simplify your task in implementing it Q: what does this look like in Haskell? *** using a monad, get syntactic sugars you can use the do syntax for monads in Haskell, if your type is made an instance of the class Monad *** abstract syntax to prevent syntax errors use an abstract syntax tree, which Haskell type-checks statically unparse this to generate human readable output, or strings to pass to servers (make an instance of Show) > data PrimExpr = > BinExpr BinOp PrimExpr PrimExpr > | UnaryExpr UnaryOp PrimExpr > | ConstExpr String > > data BinOp = OpEq | OpAnd | OpPlus to avoid having users write this abstract syntax, use combinators or functions to construct it constant :: Show a => a -> PrimExpr constant a = ConstExpr a (.+.) :: PrimExpr -> PrimExpr -> PrimExpr a .+. b = BinExpr OpPlus a b (.==.) :: PrimExpr -> PrimExpr -> PrimExpr a .==. b = BinExpr OpEq a b (.AND.) :: PrimExpr -> PrimExpr -> PrimExpr a .AND. b = BinExpr OpAnd a b so now users can write constant 3 .==. constant 5 instead of BinExpr OpEq (ConstExpr (show 3)) (ConstExpr (show 5)) ** higher-order idioms *** higher-order abstract syntax to make a binding form use functions as part of the syntax, for example, the >>= operator in monads takes a function as its right argument, which binds a name to the result of its left argument *** currying for user-customization plan for customization by having curried functions, for example, pass the precision to which equations are solved as the first argument of newtons_method *** functional representation of data for example, the behavior type in Fran (as described in Conal Elliot's paper, although the implementation is different) > type Time = Double > type Behavior a = [Time] -> [a] ** type checking tricks *** phantom types from "Domain Specific Embedded Compilers" by Daan Leijen and Eric Meijer, Second Conference on Domain-Specific Languages, Austin Texas, U.S.A., Oct. 3-5, 1999 (Usenix, 1999), page 115. to get Haskell to do some type checking for you, statically, introduced polymorphic types with type variables that must match > data Expr a = Expr PrimExpr > constant :: Show a => a -> Expr a > constant a = ConstExpr a > (.+.) :: Expr Int -> Expr Int -> Expr Int > (Expr a) .+. (Expr b) = Expr (BinExpr OpPlus a b) > (.==.) :: Eq a => Expr a -> Expr a -> Expr Bool > (Expr a) .==. (Expr b) = Expr (BinExpr OpEq a b) > (.AND.) :: Expr Bool -> Expr Bool -> Expr Bool > (Expr a) .AND. (Expr b) = Expr (BinExpr OpAnd a b) also make the type Expr an abstract data type, and hide the type PrimExpr, which ensures that the Haskell type checker will catch all potential problems ** sequencing using monads use a monad: to sequence operations, to thread state through several operations, to propagate errors, or all of the above.