CS334 PROGRAMMING LANGUAGES

Assignment 4 -- Solutions

1. Define Curry: ((S * T) -> U) -> (S-> (T -> U)) and UnCurry: (S -> (T -> U))-> ((S * T) -> U).

Solution:


       fun Curry f s t = f(s,t);
or equivalently,
        fun Curry f = fn s => fn t => f(s,t);

        fun UnCurry g (s,t) = g s t
or equivalently,
        fun UnCurry g = fn (s,t) => g s t
For the next part, a proof was required, not an example. Many of you seemed to be willing to believe the following proof that the identity function always returns primes:
Proof: Id(5) = 5, which is a prime, therefore Id(n) always returns a prime!

OK, here is a real proof that Curry and UnCurry undo each other:

(i) show UnCurry (Curry (f)) (s,t) = f (s,t).

    UnCurry (Curry f) (s,t) = (fn (s,t) => (Curry f) s t) (s,t)
                            = (Curry f) s t (by def of UnCurry)
                            = (fn s => fn t => f(s,t)) s t
                            = f (s,t)
(ii) show Curry (UnCurry (g)) s t = g s t.
    Curry (Uncurry g) s t   = (Uncurry g) (s,t)
                            = g s t
2. What are the requirements or type compatibility between a formal and actual parameter of a procedure or function in Pascal or Modula-2.

Var parameters should be name equivalent (and named), while value parameters must be assignment compatible

3. Problem 6, page 185 of Louden. You need only answer for Pascal. Be sure to explain your answers.

Both x and x^.next have type ^rc, but because the type is descriptive (rather than named), the types are considered distinct.

4. Investigate how Clu or Algol 68 handle variant records in a type-safe way. Give a careful comparison with the way variant records are handled in Ada (as explained in class). Which method would you prefer if you had the choice?

Clu requires that all access to "variant" fields be under the protection of a typecase statement. This guarantees type safety. Ada requires any change to the tag field also replace all other fields (like Clu), but may access variant fields w/out typecase. However, the system generates a run-time check. (Personally I prefer Clu's approach since it is clearer to the reader.)

5. Do problem 36, page 190 of Louden expressing the answer as an ML program:

datatype termtype = INTEGER | REAL | SUBRANGE of int*int |
            ARRAY of int*int*termtype | POINTER of termtype | 
            RECORD of (string*termtype) list;
Notice that records need labels for the fields since labels are important in checking equivalence of record types.

Now write a type_equal function (in ML) which determines if two such types are structurally equivalent (i.e., don't use Pascal equivalence, use structural equivalence).

If the records are only equal if they have the same typed fields in the same order then the function is trivial:

    fun type_equal t u = (t = u);
If the order of fields in records can vary, then it is more complex.

Please discuss the added difficulties of handling recursive types as well.

There are several problems. One is how you even write down recursive and mutually recursive type definitions. If that is solved you still have the difficulty of how you compare items which are essentially "infinite". E.g., lists can be expanded forever to get:

        datatype list = Nil | List of int*list;
                      = {Nil} U {List(n,Nil)} U {List(m,List(n,Nil))} U ... 

Also recursive def's can unwind in different ways. E.g.

        datatype list' = Even of Elist | Odd of Olist
        datatype elist = Nil | EList of int*int*elist
        datatype olist = Single of int | OList of int*int*olist
Clearly list is equivalent to list' (structurally). The algorithm to check this is quite sophisticated.

6a. What value does your interpreter return in evaluating:

        let x = 1
        in let g = fn y => succ x
           in let x = 7
              in g 0 end end end
Should be 2.

b. What is the correct answer (independent of your interpreter) for the case in which static scoping is desired. 2

c. What is the correct answer for dynamic scoping? 8

7. See on-line solution.