University of Toronto
csc324, Programming Languages, Fall 1996
Assignment 1 [10% of mark]
Out: October 8, 1996
Due: October 22, 1996, 11:59 p.m.
In particular, we will supply you with a function compile that
translates
-expressions into S K I expressions.
You will write
Scheme functions reduce-pure and reduce-applied to interpret
S K expressions: (reduce-pure (compile E))
will be the result of evaluating E in the context of the
pure
-calculus; (reduce-applied (compile E))
will be the result of evaluating E in the context of an
applied
-calculus.
This project is based on S -combinator theory, invented as a basis for mathematical logic in the 1920's by Schöfinkel and independently in the 1930's by Curry. (Remember currying - a technique for producing functions taking just one argument from functions taking two or more arguments? That's the same guy!) The S -combinator theory defines computation over (possibly infinite) input strings using only two operators: S and K. However, for this project we will use three operators, S , K and I, and thus talk about S K I expressions.
Here's the definition of S K I expressions.
The last rule shows how to write function application -- it
is the same as in
-calculus.
As before, application associates to the left,
so that
is equal to
, which is not always the
same as
.
Given an S K I expression, we may rewrite its prefix (prefix of a string is an initial substring of this string) as follows:
Here, E, F, G, X, and Y stand for arbitrary S K I subexpressions.
[Note that
is not a subexpression of
because of left-associativity:
.]
The first rule means parentheses can be dropped when not necessary.
The second rule means that the S combinator expresses function composition (F composed with G), passing argument X to both functions (and therefore duplicating information). You can prove this rewrite rule for yourself using the definition of S .
The third rule means that operator K expresses both preservation and destruction of information -- it preserves the first argument, removes the second, and preserves all expressions thereafter. Since the application associates on the left,
Finally, Iis the identity combinator. I has the same effect as S :
So, I is not really necessary but is used anyway to make expressions more concise. These expressions can be used to express any computable function from strings to strings. For example, expression S makes a duplicate of its first argument:
Notice that we reduced the first subexpression in parentheses, I x, just as if it were a top level expression.
In another example, the expression S (S )x reduces to three copies of x:
(compile '(\ x x))
-> i
(compile '((\ x x x) 4))
-> (s i i 4)
The syntax of lambda terms is as usual, except for the following differences.
\ in place of the Greek letter
Here are some examples of this syntax:Write
as (\ x + x x).Write
as (\ x (\ y x)) or as
(\ x \ y x)Write
as (\ x x (\ y x)).
Your goal is to write functions that apply S K I rewrite rules
to reduce any S K I expression to its ``normal'' form
(i.e., the expression cannot be reduced any further). When coupled with
the compile function, you will have an evaluator for the
(pure or applied)
-calculus.
In Scheme, we will represent top-level S K I applications with lists.
For example, the application S is represented in Scheme
by (s k k). Note that in the ordinary S K I rules, an application that
is itself an argument in an enclosing application
must always be surrounded by parentheses
in order to override left-associativity.
(reduce-paren '((X ...) E ...))
-> (X ... E ...)
(reduce-paren '(x))
-> x
It will only be called when its argument actually has such unnecessary parentheses. Example:
(reduce-paren '((a b c d) a b))
-> (a b c d a b)
(reduce-paren '(42))
-> 42 (reduce-i '(i x ...))
-> (x ...)
Example:
(reduce-i '(i x k s y))
-> (x k s y) (reduce-k '(k x y ...))
-> (x ...)
Example:
(reduce-k '(k (x 2) q z f))
-> ((x 2) z f) (reduce-s '(s f g x ...)
-> (f x (g x) ...)
Example:
(reduce-s '(s s i x i x))
-> (s x (i x) i x)
(reduce-pure-outer '(k (i y) x z (i 4)))
-> ((i y) z (i 4))
(reduce-pure-outer '((x y)))
-> (x y)
(reduce-pure-outer '(s x))
-> (s x)
In the last example, the S rule could not be applied because there
weren't enough arguments to the S function.(equal? (reduce-pure-outer E) E)Example:
(reduce-pure-whnf '(k i (x y) z (i 42)))
-> (z (i 42)) (reduce-pure '(s (s i i) i x))
-> (x x x)
As described earlier, (reduce-pure (compile E)) for any
lambda expression E will be the normal form of E if one exists.
(However, if the normal form of E still contains
abstractions,
then they will be expressed in S K I form.)
First, you will write functions that implement reductions for these constants.
(reduce-if '(if true x y z))
-> (x z)
(reduce-if '(if (i false) t (i e) foo bar)
-> ((i e) foo bar)
In the first example, if chose the ``then'' branch
and appended the rest of
the expression (i.e., z) to it. In the second example,
it had to reduce the conditional to normal form before seeing that it
had value false; it then chose the ``else'' branch. (reduce-iszero '(iszero 0 x))
-> (true x)
(reduce-iszero '(iszero (i 0))
-> (true)
(reduce-iszero '(iszero 64))
-> (false)
(reduce-iszero '(iszero s))
-> (false) (reduce-pred '(pred 2 x))
-> (1 x)
(reduce-pred '(pred (i 42) x))
-> (41 x) (reduce-succ '(succ 2 x))
-> (3 x) (reduce-plus '(plus 3 5 6))
-> (8 6)
(reduce-plus '(plus (plus (i 3) (i 2)) 1)
-> (6) (reduce-fix '(fix (if true 1) y))
-> ((if true 1) (fix (if true 1)) y)
Second, you will write functions reduce-applied-outer, reduce-applied-whnf, and reduce-applied which are modifications of reduce-pure-outer, reduce-pure-sequence, and reduce-pure, respectively.
(reduce-applied-outer '(succ 1 (i x))
-> (2 (i x))
(reduce-applied-outer '(4))
-> 4
(reduce-applied-outer '42)
-> 42Testing 10 (Has the program been tested accordingly?)Style 25 (The program should be written using functional programming style.)
Documentation 10 (Explanation for each module and the entire program.)
Correctness 55
David Neto will grade this assignment. He will have office hours on Friday October 11, 1996, from 4-5pm in SF3207, and on Monday October 21, 1996, from 3-4pm in SF3207.
Acknowledgment
This project was suggested by David Neto. Implementation of function compile is also due to him.