Programming Language Concepts
CS 360-001 Tuesday/Thursday 15:30-16:50 (Rush 209)
CS 360-002 Tuesday/Thursday 14:00-15:20 (Rush 209)
CS 360-003 Tuesday 18:30-21:20 (UCross 151)
Office: University Crossings 106
Office hours: Mondays 4pm–7pm; Thursdays 5pm–6pm.
CLC office hours: Tuesday 12pm–2pm; Thursday 6pm–8pm Allen Yang
CLC office hours: Wednesday 6pm–8pm
Due Friday, March 9, 11:59:59PM EST.
In this assignment, you will complete the implementation of MiniScheme we saw in lecture.
The rules for evaluating MiniScheme are a formalized version of the environment model of evaluation we saw at the beginning of the term. Although MiniScheme’s syntax looks much like Scheme’s, it’s abstract syntax is represented by an algebraic data type instead of by lists of atoms.
You should have read Chapters 12 and 13 of LYAH before beginning this homework.
Hoogle may be useful for looking up functions from the standard library.
You must complete the implementations of
eval in the file
src/MiniScheme/Eval.hs. The evaluator can be used in any monad that is an instance of the
MonadEval type class, which is defined in the same file. Any monad that has the proper type of environment and state, that can raise exceptions, and that perform IO can be an instance of
MonadEval—that is all it takes to be an “evaluation monad.” We have provided two such monads: one that provides for deterministic evaluation, and one that provides full support for non-deterministic evaluation with
amb. You don’t have to change any code to get full support for
amb—you just have to choose the right evaluation monad.
eval evaluates expressions, of type
Exp, to produce values, of type
Val. The function
evalDecl evaluates declarations, like
(define x 3), which bring new variables into scope.
evalDecl is tricky to understand; here is its type:
evalDecl :: MonadEval m => Decl -> (Maybe Val -> m a) -> m a
Its second argument is a continuation, which takes the value of the declaration and performs some additional computation. Declarations may or may not produce a value, which is why the argument of the continuation has the type
Maybe Val. Any variable brought into scope by the declaration should be visible only in the continuation.
This assignment is worth 50 points. There are 65 possible points.
There are two ways you can interact with the MiniScheme interpreter: via a REPL that evaluates MiniScheme expressions as you type them in, or via direct calls to the evaluator using
make, the MiniScheme interpreter will be available as
bin/minischeme. You may invoke it three ways:
minischeme [FILE...]will evaluate the files given as arguments using a deterministic evaluation monad.
minischeme --amb [FILE...]will evaluate the files given as arguments using a non-deterministic evaluation monad.
minischeme --interactivewill start the interactive REPL, which uses the deterministic evaluation monad.
We have provided several example programs in the
examples directory, all of which can be run with the
minischeme interpreter. The
puzzle.scm example requires full support for
amb. It can be run as follows:
./bin/minischeme --amb examples/puzzle.scm
The REPL will try to parse whatever you throw at it, which can span multiple lines. If it gets stuck parsing something illegal, you can type Ctrl-C get back to the REPL prompt. To exit the interpreter, type :quit or Ctrl-D at the REPL prompt.
stack repl. This will start
ghci and load all of the modules for the assignment. There are several helper functions that will be useful during debugging:
parseExp :: String -> Maybe Expwill parse an expression.
run :: String -> IO (Either String [Val])will parse a MiniScheme program and run it using the deterministic evaluation monad.
runAmb :: String -> IO (Either String [[Val]])will parse a MiniScheme program and run it using the non-deterministic evaluation monad.
Complete the implementation of
let in the function
Hint: Use the
extendVars function to create a local environment in which to evaluate the body of the
let. You will also want to use
mapM to evaluate the expressions that give the initial values of the let-bound variables. Here is its type signature:
mapM :: Monad m => (a -> m b) -> [a] -> m [b]
unzip will be useful to split apart the let-bound variables and the expressions that give their initial values.
Add support for
amb in the interpreter. This requires a single line of code. Make sure you read about
MonadPlus in Chapter 12 of LYAH.
Hint: You should use the function
msum from the standard library. You should not need
mapM here, but you will probably need
map! Here is the type of
msum :: MonadPlus m => [m a] -> m a
Implement support for
lambda expressions. The value of a lambda expression should be a closure. The
ClosV data constructor in the
Val data type is used to represent a closure. You will need to get the code part of the closure directly from the lambda expression. Where can you get the current environment? Just use
ask, which has this type:
ask :: MonadReader r m => m r
Finish the definition of
apply to support calling user-defined procedures. You will need to extend the environment in the closure with bindings for the parameters. Just as with
let, you will want to use
local to swap out the current environment for the environment in the closure when you evaluate the body of the lambda. It has this type:
local :: MonadReader r m => (r -> r) -> m a -> m a
How can you provide a proper argument of type
r -> r to
local? You should provide a function that ignores its argument and simply returns the environment contained in the closure. The function
const will probably be useful!
There are two separate forms of
define in MiniScheme:
(define v e) and
(define (f param ...) body). The latter is not just syntactic sugar for the former since it must permit recursive function definitions—see the big-step rules for declarations from lecture. Neither form of
define produces a value, so the continuation of
evalDecl should be called with
Nothing as an argument in both cases.
Hint: For the
DefineLamD case, you will want to explicitly allocate a store location with
alloc and then use
extendVarLoc to bind the defined function’s name to this location.
extendVarLoc has this type:
extendVarLoc :: MonadReader Env m => Var -> Loc -> m a -> m a
The third argument is its continuation. This continuation should create a closure using the current environment, which now has a binding for the named function. The easiest way to do this is to create a lambda expression using the
LamE data constructor and recursively call
eval. When you have the value of this lambda expression, set the location to point to this value using
setVal. Then invoke
k! This part of the problem is hard!
How long did spend on each problem? Please tell us in your
README.md. You may enter time using any format described here.