-Calculus Evaluator
This homework assignment aims to implement an evaluator of
The interpreter should be implemented as a Haskell module called Hw3
. Note the capital H
. The module names in Haskell have to start with capital letters. As the file containing the module code must be of the same name as the module name, all your code is required to be in a single file called Hw3.hs
. Your file Hw3.hs
has to start with the following lines:
module Hw3 where
type Symbol = String
data Expr = Var Symbol
| App Expr Expr
| Lambda Symbol Expr deriving Eq
The first line defines a module of the name Hw3
. The names of variables in String
. The second line just introduces a new name Symbol
for the type String
to distinguish visually when we deal with the variable names. The last line defines the data type representing deriving Eq
makes the data type instance of the Eq
class so that it is possible to check whether two
Evaluator specification
First, make the data type Expr
an instance of the Show
class so that ghci
can display show
function converting ghci
prompt the following expressions, it should behave as follows:
λ> Var "x"
x
λ> App (Var "x") (Var "y")
(x y)
λ> Lambda "x" (Var "x")
(\x.x)
λ> App (Lambda "x" (Var "x")) (Var "y")
((\x.x) y)
λ> Lambda "s" (Lambda "z" (App (Var "s") (App (Var "s") (Var "z"))))
(\s.(\z.(s (s z))))
So the symbol \
. Variables are displayed as their names. Applications are displayed as (e1 e2)
with a space separating expressions e1,e2
and (\x.e)
.
As a next step, your task is to implement a function eval :: Expr -> Expr
. This function for a given input
To reduce a redex, you have to implement the substitution mechanism allowing you to substitute a
The last case deals with the name conflicts, i.e., it uses
Your code has to generate fresh symbols when needed. The fresh symbols can be denoted, e.g., "a0","a1","a2",...
. To generate a fresh symbol, it suffices to increment the number of the last used symbol. This number is a state of your computation. As Haskell is a purely functional language, you cannot have a state and update it when necessary. Instead, you need to include the index into signatures of your functions similarly, as we did in the exercise from Lab 9 where we implemented a function indexing nodes of a binary tree.
Test cases
Below you find several public test cases. If the eval
function just returns its input.
λ> eval (App (Var "x") (Var "y"))
(x y)
λ> eval (Lambda "x" (Var "x"))
(\x.x)
If it is reducible, it returns its normal form. For instance
λ> eval (App (Lambda "x" (Var "x")) (Var "y"))
y
Consider the expression
λ> eval (App (Lambda "x" (Lambda "y" (App (Var "x") (Var "y")))) (Var "y"))
(\a0.(y a0))
Consider the
λ> eval (App (Lambda "x" (Lambda "y" (Var "y"))) (Var "y"))
(\a0.a0)
Consider the
ex = App (Lambda "x"
(Lambda "y"
(Lambda "z" (App (App (Var "x") (Var "y")) (Var "z")))))
(App (Var "y") (Var "z"))
λ> eval ex
(\a0.(\a1.(((y z) a0) a1)))
To write more complex test cases, you can define subexpressions and then compose more complex ones. For instance, to test that
one = Lambda "s" (Lambda "z" (App (Var "s") (Var "z")))
suc = Lambda "w"
(Lambda "y"
(Lambda "x"
(App (Var "y")
(App (App (Var "w") (Var "y"))
(Var "x")))))
λ> eval (App suc one)
(\y.(\x.(y (y x))))
One more test case using the definition
It reduces to
λ> eval (App (Lambda "z" one) (App (Var "s") (Var "z")))
(\a0.(\z.(a0 z)))