CS 360
Winter 2018
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)

Geoffrey Mainland
Office: University Crossings 106
Office hours: Mondays 4pm–7pm; Thursdays 5pm–6pm.
Teaching Assistant:
Xiao Han
CLC office hours: Tuesday 12pm–2pm; Thursday 6pm–8pm
Allen Yang
CLC office hours: Wednesday 6pm–8pm
Warning! This material is for an old version of the course.

Homework 6: Implementing MiniScheme

Due Friday, March 9, 11:59:59PM EST.

Accept this assignment on GitHub Classroom here. The standard homework instructions apply.

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 evalDecl and 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.

The function 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.

Working with the Interpreter

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 ghci.

Using the REPL

After typing make, the MiniScheme interpreter will be available as bin/minischeme. You may invoke it three ways:

  1. minischeme [FILE...] will evaluate the files given as arguments using a deterministic evaluation monad.
  2. minischeme --amb [FILE...] will evaluate the files given as arguments using a non-deterministic evaluation monad.
  3. minischeme -i or minischeme --interactive will 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.

Using ghci

To use ghci, type 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:

  1. parseExp :: String -> Maybe Exp will parse an expression.
  2. run :: String -> IO (Either String [Val]) will parse a MiniScheme program and run it using the deterministic evaluation monad.
  3. runAmb :: String -> IO (Either String [[Val]]) will parse a MiniScheme program and run it using the non-deterministic evaluation monad.

Problem 1: Implement let (10 points)

Complete the implementation of let in the function eval.

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.

Problem 2: Implement amb (10 points)

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

msum :: MonadPlus m => [m a] -> m a

Problem 3: Implement lambda (10 points)

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

Problem 4: Implement application of user-defined procedures (10 points)

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 extendVars.

Hint: 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!

Problem 5: Implement define (20 points)

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 evalDecl’s continuation k! This part of the problem is hard!

Problem 6: Homework Statistics (5 point)

How long did spend on each problem? Please tell us in your README.md. You may enter time using any format described here.