Haskell代写 | COMP3258: Functional Programming

1. Expression Trees

This time our expression trees are about lists instead of integers. And we are going to define an evaluation function which returns a value of Maybe [Int].

In Haskell, Maybe is a type constructor used to represent “a value or nothing”. A value of type Maybe a is either Just a (just a value of type a) or Nothing, as Maybe has 2 data constructors by the following definition (which is included in Prelude library).

Maybe describes values with a context of possible failure attached. For example, if we expect a function returns a integer, but it might have error, we can set its return type to be Maybe Int, and let it returns Nothing when error happens. When there is no error, the function can return Some 3 for example if 3 is the calculated result.

If the input type of your function is Maybe a, you need to take care of the 2 possible forms of the argument. The following example shows how to use pattern matching to define a function f that converts Nothing to -1.

Let’s start from defining operations.

We support 3 operations on lists. Add and Sub can be understood as vector addition and substraction. And Div works in a similar way. Essentially, an operation takes 2 lists with the same lengths. For each element from one list, add it to, or substract it from, or divide it by the element from in the same position from the other list. Specially, if the 2 operands are both empty lists, the result should be empty list.

The function should return Nothing when: Any of its input is Nothing, or its input lists are in different lengths, or dividing by zero happens.

Expected running results:

*Main> applyOp Add (Just [1,2,3]) (Just [1,1,1]) Just [2,3,4]

*Main> applyOp Sub (Just [1,2,3]) (Just [1,1,1]) Just [0,1,2]

*Main> applyOp Div (Just [2,3,4]) (Just [2,2,2]) Just [1,1,2]

*Main> applyOp Div (Just []) (Just []) Just []

*Main> applyOp Div (Just [2,3,4]) (Just [2,0,2]) Nothing

*Main> applyOp Add (Just [1,2]) (Just [1,1,1]) Nothing

*Main> applyOp Sub (Just [1,2,3]) Nothing Nothing

Our Expr data type is defined for expression trees on lists.

Problem 2. (5 pts.) Implement a function eval :: Env -> Expr -> Maybe [Int] to evaluate expression trees. eval should return Nothing if the lengths of 2 lists for one operation is different, or any divisor is 0, or a variable cannot be found in the environment.

Now we are going to implement a general function that works for any Parser. Instead of parse, this function runs a parser and returns the parsed thing, but it fails when the parser does not parse the whole string. Thurs, for example, it can tell if a given string is grammacially incorrect.

data Parser a = P (String -> [(a,String)])

In the data type definition of Parser in Parsing.h, we are using empty list to represent failure of a parsing process. In our function, we would like to convert it to Nothing.

Problem 5. (5 pts.) Implement a function runParser :: Parser a -> String -> Maybe a. runParser runs a given parser once to parse the full string and returns the parsed result.

Instructions contains integer literal IVal, variable Var, and binary operations IBin. The evaluation of instructions are based on a simple stack, which is modeled as a list of list of integers:

type Stack = [[Int]]

Initially the stack would be empty, when implementing instruction, the state of the stack changes. Instruction IVal xs will push xs into the stack, and IVar x will push the value of variable x (if it is in the environment) into stack. IBin op will pop two values from the stack, and apply the binary operator to them, and then push the result into the stack.

type Prog = [Instr]

A program is a list of instructions. Implementing a program is implementing instructions in it sequentially.

Before compiling, we can do static checking on an expression to catch some possible errors eariler. It is static in contrast with dynamtic checking because we do not need to run the program or calculate the result of expressions.

Problem 6. (5 pts.) Implement a function check :: Expr -> Env -> Bool that when any binary operations in the input expression takes 2 lists with different lengths, or when any variable cannot be found in the enviroment.

Problem 7. (10 pts.) Implement a function compile :: Expr -> Env -> Maybe Prog that compiles an expression into a program that can be run in the stack machine after checking the expression with given environment using check. It returns Nothing when checking fails.

After compiling successfully, we need a function to run the program. We assume the input program and environment together have passed checking as its compiled by our compile function. But there still could be runtime error.

With all those functions defined so far, you should be able to verify that the two ap- proaches always give the same result:

Direct evaluation over an expression

First translate the expression into a program on a simple stack machine, and then run the

*Main> let Just exp = runParser pExpr “[3,2]-[1,0]”

*Main> exp

Bin Sub (Val [3,2]) (Val [1,0])

*Main> eval [] exp Just [2,2]

*Main> check exp [] True

*Main> let Just prog = compile exp []

*Main> prog

[IVal [1,0],IVal [3,2],IBin Sub]

*Main> runProg prog [] Just [2,2]

4. REPL: Read-Eval-Print Loop

In previous programs, we are required to pass an environment to the evaluation process manually, which is quite inconvenient. Also there is no way for us to change the value of a variable during the calculation.

In this section we are going to develop a very simple REPL to compute the value of an expression. A REPL is an interactive program, which reads the input, executes the input, prints the result and waits for the next input. For example, GHCi is the REPL for Haskell.

The REPL stores values of variables in Env, therefore we can directly refer to those variables during calculation. To hide the details about IO, you can paste the following code to the assignment:

If you enter main in GHCi, it enters the REPL with an empty environment. The REPL prints a prompt > and waits for the command from the user.

*Main> main

>

You job is to implement the dispatch method that takes the current environment and

the command from the user, executes the command, prints the result, and goes back to REPL. The following functions are provided to take care of IO things for you:

quit :: IO () quit = return ()

loop :: String -> Env -> IO () loop str next = do

putStrLn str repl next

When you call the function quit from dispatch, it will exit the REPL. You can pass a String and current environment to loop, which will print the String for you and continue to REPL with the environment you provided.

What is more, you can call the show function to convert a list of integers to a string when you need.

Problem 9. (20 pts.) Please complete the dispatch function. You need to support 4 types of commands to get full marks. If any illegal case occurs, just print Error and continue to REPL, for example, when receiving illegal commands.