CS 334
Programming Languages
Spring 2002

Assignment 2
Due Thursday, 2/21/00


  1. A Pascal program corresponding to the fast power function presented in the last assignment is as follows:
        function exp(base, power: integer): integer;
        var ans:integer
        begin
            ans:= 1;
            while power > 0 do
                if odd(power) then
                    begin
                        ans := ans*base;
                        power:= power- 1
                    end
                else
                    begin
                        base := base * base;
                        power := power div 2
                    end;
            exp := ans
        end;
    
    Please mimic the translation given in class to convert this Pascal program to ML. (Note you will obtain a different - and slightly more efficient - version of the program than the natural recursive solution in the previous assignment.)

  2. One of the advantages of functional languages is the ability to write high-level functions which capture general patterns. For instance, in class we defined the "listify" function which could be used to make a binary operation apply to an entire list.

    a. Your assignment is to write a high-level function to support list abstractions. The language Miranda allows the user to write list abstractions of the form:

       [f(x) | x <- startlist; cond(x)]
    

    where startlist is a list of type 'a, f : 'a -> 'b (for some type 'b), and cond : 'a -> bool. This expression results in a list containing all elements of the form f(x), where x is an element in the list, "startlist", and "cond(x)" is true. For example, if sqr(x) = x*x and odd(x) is true iff x is an odd integer, then

        [sqr(x) | x <- [1,2,5,4,3]; odd(x)]
    

    returns the list [1,25,9] (that is, the squares of the odd elements of the list - 1,5,3). Note that the list returned preserves the order of startlist.

    You are to write a function

        listcomp : ('a -> 'b) -> ('a list) -> ('a -> bool) -> ('b list)
    
    so that
        listcomp f startlist cond = [f(x) | x <- startlist; cond(x)].  
    

    (Hint: One way to do this is to divide the function up into two pieces, the first of which calculates the list, [x | x <- startlist; cond(x)], and then think how the "map" function can be used to compute the final answer. It's also pretty straightforward to do it all at once.)

    b. Test your function by writing a function which extracts the list of all names of managerial employees over the age of 60 from a list of employee records, each of which has the following fields: "name" which is a string, "age" which is an integer, and "status" which has a value of managerial, clerical, or manual. (Be sure to define this function correctly. I'm always amazed at the number of people who miss this problem by carelessness!)

    c. Generalize your function in part a to

        listcomp2 g slist1 slist2 cond = 
                                [g x y | x <- list1; y <- list2; cond x y].
    
  3. Write a program, parenbalance: string ->, which determines if a string including various kinds of parentheses is balanced. For example,
       - parenBalance "(a*[b-(c/d)]-r)"; 
       val it = true : boolean 
       - parenBalance "(a*[b-(c/d]] -r)"; 
       val it = false : boolean 
       - parenBalance "())(()"; 
       val it = false : boolean
    
    Use a stack to check for matching parentheses (yes, I know it might seem easier to use a general list, but I want you to get some experience with user-defined types). The general idea is to push left parentheses on the stack and then pop them off when the corresponding right parenthesis is encountered. Your function should treat "(", ")" and "[", "]" as parenthesis pairs. All other characters should be ignored.

    Use the definition:

        datatype 'a stack = Empty | Push of 'a * ('a stack )
    

    and be sure to write a pop routine which returns the stack after the top element is removed. (Hint: It is trivial if you use pattern matching.) You can define pop Empty = Empty, or, if you are willing to learn about exceptions on your own, raise an exception.

    You may find it useful to use the built-in function, explode, which takes a string to a list of the characters in the list. E.g., explode "hello" = [#"h",#"e",#"l",#"l",#"o"].

  4. In this problem, you will write an ML program which evaluates simple arithmetic expressions. Our expression language is very simple. It involves expressions written in the language given by the following simple BNF grammar.

            e ::= n | id | ~e | e + e | e - e | e * e | e / e | (e) 
    
    In the above, "n" stands for an integer, "id" is an identifier, "~e" is the negative of e, the next four terms are the sum, difference, product, and quotient of expressions, while (e) is used to determine the order of evaluation of expressions (e.g. 3 * (5 - 1)).

    Rather than working directly with the syntax above, we will presume that we have a parser which parses input into an abstract syntax tree, which your interpreter should use. Abstract syntax trees are just a convenient method of expressing parsed expressions; the definition of the ML datatype is

        datatype arithExp = 
                    AST_NUM of int | AST_NEG of arithExp | 
                    AST_PLUS of  (arithExp * arithExp) |
                    AST_MINUS of  (arithExp * arithExp) |
                    AST_PRODUCT of  (arithExp * arithExp) |
                    AST_QUOTIENT of  (arithExp * arithExp) |
                    AST_ID of string | AST_ERROR of string;
    
    Note how this definition mirrors the BNF grammar given above; for instance, the constructor AST_NUM makes an integer into an arithExp, and the constructor AST_PLUS makes a pair of arithExp's into an arithExp representing their sum. Interpreting abstract syntax trees is much easier than trying to interpret terms directly. The only term not reflected in the grammar above is AST_ERROR. It is used by my parser (see below) to reflect a term that is not syntactically correct.

    Note that we no longer need the parentheses to group terms, as the example given above would simply be represented by:

            AST_PRODUCT (AST_NUM 3, AST_MINUS(AST_NUM 5,AST_NUM 1))
    
    which represents the parse tree which looks like:

    You are to write an ML function evaluate that takes an abstract syntax tree representing an arithExp and returns the result of evaluating it. Most of the time when you evaluate the tree, you will get an integer, but if evaluating the expression results in a division by zero, it should return an error message. To allow both of these to be returned, we define

            datatype arithValue = NUM of int | ERROR;
    
    Thus you will define evaluate: arithExp -> arithValue, where as usual, the parse trees where the operation at the root is a binary operator are evaluated by evaluating the left and right subtrees and then performing the appropriate operation on the results. Evaluating AST_NUM expressions should be trivial, and AST_NEG's aren't much harder. Notice that if any subtree evaluates to ERROR, then the whole tree should evaluate to ERROR. For simplicity, please evaluate AST_ID and AST_ERROR terms as resulting in ERROR. (We'll talk later about how to handle identifiers.)

    To get you started, here is the beginning of the definition of evaluate:

            fun evaluate (AST_NUM n) = NUM n
                evaluate (AST_ERROR str) = ERROR
              | evaluate ...
    

    Finally, to make your life easier, I have written a parser in ML that will take a correctly written arithmetic expression and return the equivalent element of type arithExp. Thus you can use this to create examples to try out your evaluate function.

    If you wish to use the parser, copy it from the link above and save it to a file named "parseArith.ml" in the same directory as your homework. Now the file containing your evaluate function should start with:

        use "parseArith.ml";
    
        fun evaluate (AST_NUM n) = NUM n
          | evaluate ...  
    
    Because parseArith.ml contains a definition of the type arithExp. you must NOT include another copy of the type definition for arithExp or the parser will not produce elements of a type that the evaluation function can handle.

    To use the parser, you apply the function parsestr to a string representing an arithmetic expression and you get out the corresponding element of type arithExp:

    - parsestr "34-7*(5-2)";
    val it =
      AST_MINUS
        (AST_NUM 34,AST_PRODUCT (AST_NUM 7,AST_MINUS (AST_NUM 5,AST_NUM 2)))
      : arithExp
    - evaluate it;
    
    or you can use parsestr directly with the evaluate function:
    - evaluate (parsestr "34-7*(5-2)");
    val it = NUM 13;
    


Back to:
  • CS 334 home page
  • Kim Bruce's home page
  • CS Department home page
  • kim@cs.williams.edu