% -*- mode: LaTeX; compile-command: "mk" -*- \documentclass{tufte-handout} %include polycode.fmt %\arrayhs \usepackage{../../hshw} \usepackage{mathpartir} \title{\thecourse\ problem set 10} \date{\small Revision 1: compiled \today\ at \currenttime} \author{Due Tuesday, April 5} \begin{document} \maketitle \begin{itemize} \item Files you should submit: |Calc.hs|, containing a module of the same name. \end{itemize} As we have seen in class, Haskell's \emph{type classes} provide \emph{ad-hoc polymorphism}, that is, the ability to decide what to do based on the type of an input. This homework explores one interesting use of type classes in constructing \emph{domain-specific languages}. \begin{center} \includegraphics[width=2in]{hp46} \end{center} \section{Expressions} \label{sec:expr} On day one of your new job as a software engineer, you've been asked to program the brains of the company's new blockbuster product: a calculator. But this isn't just any calculator! Extensive focus group analysis has revealed that what people really want out of their calculator is something that can add and multiply integers. Anything more just clutters the interface. Your boss has already started by modeling the domain with the following data type of arithmetic expressions: \begin{code} data ExprT where Lit :: Integer -> ExprT Add :: ExprT -> ExprT -> ExprT Mul :: ExprT -> ExprT -> ExprT deriving (Show, Eq) \end{code} This type is capable of representing expressions involving integer constants, addition, and multiplication. For example, the expression $(2 + 3) \times 4$ would be represented by the value $|Mul (Add (Lit 2) (Lit 3)) (Lit 4)|.$ Your boss has already provided the definition of |ExprT| in \texttt{ExprT.hs}, so as usual you just need to add |import ExprT| to the top of your file. However, this is where your boss got stuck. \exercise Write Version 1 of the calculator: an evaluator for |ExprT|, with the signature\marginnote{For extra brownie points, first write a fold for |ExprT| and use it to implement |eval|.} \begin{code} eval :: ExprT -> Integer \end{code} For example, |eval (Mul (Add (Lit 2) (Lit 3)) (Lit 4)) == 20|. \exercise The UI department has internalized the focus group data and is ready to synergize with you. They have developed the front-facing user-interface: a parser that handles the textual representation of the selected language. They have sent you the module |Parser.hs|, which exports |parseExp|, a parser for arithmetic expressions. If you pass the constructors of |ExprT| to it as arguments, it will convert |String|s representing arithmetic expressions into values of type |ExprT|. For example: \begin{verbatim} *Calc> parseExp Lit Add Mul "(2+3)*4" Just (Mul (Add (Lit 2) (Lit 3)) (Lit 4)) *Calc> parseExp Lit Add Mul "2+3*4" Just (Add (Lit 2) (Mul (Lit 3) (Lit 4))) *Calc> parseExp Lit Add Mul "2+3*" Nothing \end{verbatim} Leverage the assets of the UI team to implement the value-added function \begin{code} evalStr :: String -> Maybe Integer \end{code} which evaluates arithmetic expressions given as a |String|, producing |Nothing| for inputs which are not well-formed expressions, and |Just n| for well-formed inputs that evaluate to $n$. \exercise Good news! Early customer feedback indicates that people really do love the interface! Unfortunately, there seems to be some disagreement over exactly how the calculator should go about its calculating business. The problem the software department (\emph{i.e.} you) has is that while |ExprT| is nice, it is also rather inflexible, which makes catering to diverse demographics a bit clumsy. You decide to abstract away the properties of |ExprT| with a type class. Create a type class called |Expr| with three methods called |lit|, |add|, and |mul| which parallel the constructors of |ExprT|. Make an instance of |Expr| for the |ExprT| type, in such a way that \begin{code} mul (add (lit 2) (lit 3)) (lit 4) :: ExprT == Mul (Add (Lit 2) (Lit 3)) (Lit 4) \end{code} Think carefully about what types |lit|, |add|, and |mul| should have.\marginnote{It may be helpful to consider the types of the |ExprT| constructors.} \begin{rem} Take a look at the type of the foregoing example expression: \begin{verbatim} *Calc> :t mul (add (lit 2) (lit 3)) (lit 4) Expr a => a \end{verbatim} What does this mean? The expression |mul (add (lit 2) (lit 3)) (lit 4)| has \emph{any type} which is an instance of the |Expr| type class. So writing it by itself is ambiguous: GHC doesn't know what concrete type you want to use, so it doesn't know which implementations of |mul|, |add|, and |lit| to pick. One way to resolve the ambiguity is by giving an explicit type signature, as in the above example. Another way is by using such an expression as part of some larger expression so that the context in which it is used determines the type. For example, we may write a function |reify| as follows: \begin{code} reify :: ExprT -> ExprT reify = id \end{code} To the untrained eye it may look like |reify| does no actual work! But its real purpose is to constrain the type of its argument to |ExprT|. Now we can write things like \begin{code} reify $mul (add (lit 2) (lit 3)) (lit 4) \end{code} %$ at the |ghci| prompt. \end{rem} \exercise The marketing department has gotten wind of just how flexible the calculator project is and has promised custom calculators to some big clients. As you noticed after the initial roll-out, everyone loves the interface, but everyone seems to have their own opinion on what the \emph{semantics} should be. Remember when we wrote |ExprT| and thought that addition and multiplication of integers was pretty cut and dried? Well, it turns out that some big clients want customized calculators with behaviors that they have decided are right for them. The point of our |Expr| type class is that we can now write down arithmetic expressions \emph{once} and have them interpreted in various ways just by using them at various types. Make instances of |Expr| for each of the following types: \vspace{0.5cm} \begin{tabular*}{8in}{rlcp{3in}} \textbullet & |Integer| & --- & works like the original calculator\\ \\ \textbullet & |Bool| & --- & every literal value less than or equal to $0$ is interpreted as |False|, and all positive |Integer|s are interpreted as |True|; addition'' is logical or, multiplication'' is logical and\\ \\ \textbullet & |MinMax| & --- & addition'' is taken to be the |max| function, while multiplication'' is the |min| function\\ \\ \textbullet & |Mod7| & --- & all values should be in the ranage $0 \dots 6$, and all arithmetic is done modulo 7; for example, $5 + 3 = 1$. \end{tabular*} \vspace{0.5cm} The last two variants work with |Integer|s internally, but in order to provide different instances, we wrap those |Integer|s in |newtype| wrappers. These are used just like the |data| constructors we've seen before. \begin{code} newtype MinMax = MinMax Integer deriving (Eq, Show) newtype Mod7 = Mod7 Integer deriving (Eq, Show) \end{code} % There are several possible implementations one could imagine for % |Saturated|; just make sure that % \begin{itemize} % \item |lit| creates a |Saturated| value in the proper range % \item The implementations of |add| and |mul| are compatible'' with % the usual addition and multiplication; that is, % % % |add (lit x) (lit y) == lit (x + y)|, and similarly for |mul|. % \end{itemize} Once done, the following code should demonstrate our family of calculators: \begin{code} testExp :: Expr a => Maybe a testExp = parseExp lit add mul "(3 * -4) + 5" testInteger = testExp :: Maybe Integer testBool = testExp :: Maybe Bool testMM = testExp :: Maybe MinMax testSat = testExp :: Maybe Mod7 \end{code} Try printing out each of those tests in |ghci| to see if things are working. It's great how easy it is for us to swap in new semantics for the same syntactic expression! \exercise \label{ex:hardware} The folks down in hardware have finished our new custom CPU, so we'd like to target that from now on. The catch is that a stack-based architecture was chosen to save money. You need to write a version of your calculator that will emit assembly language for the new processor. The hardware group has provided you with |StackVM.hs|, which is a software simulation of the custom CPU. The CPU supports six operations, as embodied in the |StackExp| data type: \begin{code} data StackExp where PushI :: Integer -> StackExp PushB :: Bool -> StackExp AddOp :: StackExp MulOp :: StackExp AndOp :: StackExp OrOp :: StackExp deriving Show type Program = [StackExp] \end{code} |PushI| and |PushB| push values onto the top of the stack, which can store both |Integer| and |Bool| values. |AddOp|, |MulOp|, |AndOp|, and |OrOp| each pop the top two items off the top of the stack, perform the appropriate operation, and push the result back onto the top of the stack. For example, executing the program \begin{code} [PushB True, PushI 3, PushI 6, Mul] \end{code} will result in a stack holding |True| on the bottom, and |18| on top of that. If there are not enough operands on top of the stack, or if an operation is performed on operands of the wrong type, the processor will melt into a puddle of silicon goo. For a more precise specification of the capabilities and behavior of the custom CPU, consult the reference implementation provided in |StackVM.hs|. Your task is to implement a compiler for arithmetic expressions. Simply create an instance of the |Expr| type class for |Program|,\marginnote{Note that in order to make an instance for |Program| (which is a type synonym) you will need to enable the |TypeSynonymInstances| language extension, which you can do by adding \begin{code} {-# LANGUAGE TypeSynonymInstances #-} \end{code} at the top of your file.} so that arithmetic expressions can be interpreted as compiled programs. For any arithmetic expression |exp :: Expr a => a| it should be the case that \begin{code} stackVM exp == Right [IVal exp] \end{code} Finally, put together the pieces you have to create a function \begin{code} compile :: String -> Maybe Program \end{code} which takes |String|s representing arithmetic expressions and compiles them into programs that can be run on the custom CPU. \exercise Some users of your calculator have requested the ability to give names to intermediate values and then reuse these stored values later. To enable this, you first need to give arithmetic expressions the ability to contain variables. Create a new type class |HasVars a| which contains a single method |var :: String -> a|. Thus, types which are instances of |HasVars| have some notion of named variables. Start out by creating a new data type |VarExprT| which is the same as |ExprT| but with an extra constructor for variables. Make |VarExprT| an instance of both |Expr| and |HasVars|. You should now be able to write things like \begin{code} *Calc> add (lit 3) (var "x") :: VarExprT \end{code} But we can't stop there: we want to be able to interpret expressions containing variables, given a suitable mapping from variables to values. For storing mappings from variables to values, you should use the |Data.Map| module. Add \begin{code} import qualified Data.Map as M \end{code} at the top of your file. The |qualified| import means that you must prefix |M.| whenever you refer to things from |Data.Map|. This is standard practice, since |Data.Map| exports quite a few functions with names that overlap with names from the |Prelude|. Consult the |Data.Map| documentation\marginnote{\url{http://hackage.haskell.org/packages/archive/containers/latest/doc/html/Data-Map.html}} to read about the operations that are supported on |Map|s. Implement the following instances: \begin{code} instance HasVars (M.Map String Integer -> Maybe Integer) instance Expr (M.Map String Integer -> Maybe Integer) \end{code} \marginnote{To write these instances you will need to enable the |FlexibleInstances| language extension by putting \begin{code} {-# LANGUAGE FlexibleInstances #-} \end{code} at the top of your file.} The first instance says that variables can be interpreted as functions from a mapping of variables to |Integer| values to (possibly) |Integer| values. It should work by looking up the variable in the mapping. The second instance says that these same functions can be interpreted as expressions (by passing along the mapping to subexpressions and combining results appropriately). Once you have created these instances, you should be able to test them as follows: \begin{code} withVars :: [(String, Integer)] -> (M.Map String Integer -> Maybe Integer) -> Maybe Integer withVars vs exp = exp $M.fromList vs \end{code} \begin{code} *Calc> :t add (lit 3) (var "x") add (lit 3) (var "x") :: (Expr a, HasVars a) => a *Calc> withVars [("x", 6)]$ add (lit 3) (var "x") Just 9 *Expr> withVars [("x", 6)] $add (lit 3) (var "y") Nothing *Calc> withVars [("x", 6), ("y", 3)]$ mul (var "x") (add (var "y") (var "x")) Just 54 \end{code} \end{document}