CS558 Homework #5
Due 5:00 PM, Monday, February 16, 2015
Although HW #5 is not due until the 16th, the material it
covers is fair game for the mid term exam on Feb. 9, 2015.

Homework must be submitted via D2L. All submitted files (3 for this assignment sol5A.e5, hw5A.hs, and sol5B.hs) must be submitted in the appropriate D2L directory in the drop box HW5.It is your responsibility to submit the homework in the proper format with the proper names. For example.

-- Homework 5  Tom Smith   tom_smith@gmail.com

All programs mentioned can be downloaded from the this document

This homework has two parts. First, altering the definitional interpreter for a small functional language, and Second, writing a few small programs in a functional language (Haskell).

  1. Consider the following functional language, which we'll call E5.
    prog := { decl } 'in' exp
    
    exp := var
    | int
    | char
    | '(' '=' exp exp ')'
    | '(' 'if' exp exp exp ')'
    | '(' 'let' { decl } exp ')'
    | '(' '@' exp exp { exp } ')'
    | '(' '+' exp exp ')'
    | '(' '-' exp exp ')'
    | '(' '*' exp exp ')'
    | '(' '/' exp exp ')'
    | '(' '<=' exp exp ')'
    | '(' 'pair' exp exp ')'
    | '(' 'fst' exp ')'
    | '(' 'snd' exp ')'
    | '(' 'ispair' exp ')'
    | '(' 'ischar' exp ')'
    | '(' 'ispair' exp ')'
    | '(' 'isint' exp ')'
    
    var := letter { letter | digit }
    
    decl := '(' 'fun' var '(' { var } ')' exp ')'
    | '(' 'val' var exp ')'
    

    As usual, comments may be included by enclosing them between comment braces '{-' and '-}' characters, and they may be nested.

    E5 is similar to some of our earlier languages (especially E4), it retains characters and their operations, but lacks imperative features including assignment, while , block , and write.

    The local expression has been renamed let (to emphasize that it acts like an Haskell-style immutable binding). A let contains a series of zero or more declarations, the keyword 'in', and then an expression which acts as the body of the let. The value of a let is the value of its body, BUT, the body is evaluated in an enriched environment, which contains local bindings for the values and functions introduced by the let's declarations.

    There are two kinds of declarations: 'fun' and 'val'. A 'val' declaration is much like a 'local' declaration from language E4. A declaration (val x exp), extends the environment with the name 'x', binding it to the value of 'exp'.

    The most important changes are in the treatment of functions, which can be defined in two ways.

    1. The most common is the use of the function declaration '(' 'fun' var { var } exp ')' in a 'let' expression. for example, to evaluate (let (fun f (x y) (+ x (- y 1))) in (@ f 2 6)), first create a binary function 'f' value whose formal arguments are 'x' and 'y' , whose body is (+ x (- y 1)), and whose environment is the current environment; then bind f to that function value and evaluate (@ f 2 6)) in the resulting local environment.
    2. The other way is called an anonymous function, or lambda expression '(' 'lambda' '(' {var } ')' exp ')'. For example: (\ (x y) exp) introduces a nameless function with two formal parameters 'x' and 'y' and a body 'exp'.

    Functions are treated as just another kind of value, and they share the same name space as other values.

    Applications now take an arbitrary expression in the function position; this must evaluate to a function value. Functions take an arbitrary number of additional arguments (including zero). Functions are completely "first-class", i.e., they can be passed as arguments to, or returned as results of, other functions, and can be stored in pairs. A program is a series of declarations, the keyword 'in' follwoed by an expression. The value of the program is the value of this expression, evaluated in an environment created by the declarations.

    The web site gives several example E5 programs. Program static.e5 illustrates that nested functions use static scoping rules; program compose.e5 shows how to write a higher-order function that composes two existing functions.

    An E5 interpreter in Haskell (only) has been provided ( hw5.hs ).

    In the E5 language there are exactly 4 kinds of values. Ints, Characters, Pairs, and Functions. There are 3 predicates on values: ispair, ischar, and isint. With these you can test what kind of value any object takes. In addition, the interpreter for E5 attempts to recognize the convention we have used to encode lists. Recall that convention, it uses right-nested pairs ending in 0. For example (pair 4 (pair 'z' 0)) evaluates to (4.('z'.0)). In the interpreter for E5 if such a right-nested pair is recognized it is printed as [4,'z']. The interpreter also recognizes right-nested pairs of characters, and prints them as strings. For example (pair 'a' (pair 'b' (pair 'c' 0))) prints as "abc". There are still only 4 kinds of values, though we print some of them using the list conventions. Here are some examples

    1. (pair 4 (pair 5 (pair 9 0))) prints as [4,5,9] .
    2. (pair 3 (pair 5 6)) print as (3.(5.6)). Note that while it is right nested, it does not end in 0, so the list form is not used.
    3. (pair 'd' (pair 'z' (pair '3' 0))) print as "dz3".
    4. (pair 'a' (pair 'b' (pair 5 0))) prints as ['a','b',5]. Note that is starts out looking like a list of Character, but the last element is not a Character, so we can't print it as a string. But it stills looks like a list.

    What to do.

    You will need to write some small E5 programs. You will also modify the definitional interpreter. You might try adding the list convention functions to an E5 program. While the idea is the same, the format of definitions is very different in E5 than in E3 and E4. This would be good practice before you start.

    Note that this interpreter has some significant changes from the previous one for E4. We no longer use a stack, just an environment and a heap. There is a (single) environment (because both functions and other values can now be stored in the same name space). The environment now maps all identifiers directly to addresses in the heap. But, we use addresses only when we allocate new cells in the heap, We never change values in the heap since there is no assignment (or call by reference).

  2. Implement the following functions in Haskell using the Haskell function foldr, without using explicit recursion. Submit your solutions to both the exercises in a single file sol5B.hs.