Fall 98, CSE 520: Lecture 12 (Oct 6)


Correctness and completeness of eager and lazy semantics

In previous lecture we have seen two operational semantics for PCF, which is an extension of the lambda calculus. It is natural to ask what is the correspondence between the notion of beta reduction and these semantics, when restricted to the sub-language corresponding to the lambda calculus,

Theorem (Soundness of eager semantics) If M is a lambda term, and M eval N holds in the early semantics, then M ->> N (M beta-reduces to N).

The vice versa does not hold. Take for instance the term M = (\x y. y) P N, where P is any term whose evaluation does not terminate (for example, Y). We have that M beta-reduces to N, but, if the evaluation of P does not terminate, then the evaluation of M does not terminate either.

For the lazy semantics, on the contrary, also the other directions holds, at least in a weak form:

Theorem (Soundness and weak completeness of the lazy semantics) If M is a lambda term, and M eval N holds in the lazy semantics, then M ->> N holds. Viceversa, if M ->> N holds, then M eval P holds in the lazy semantics for some P such that P ->> N.

In the completeness part of the above theorem, in general N is different from P. Consider for instance M = \x. P, where P ->> Q. We have that M ->> \x. Q, but we cannot evaluate M to (\x. Q), since M is already in canonical form.

Simulating lazy functions in eager languages

In a higher order eager language, it is in general possible to simulate lazy functions by encoding its arguments as abstractions (i.e. functions). In fact, abstractions are canonical forms, hence they are not evaluated.

For instance, we can encode streams and its constructors/selectors in ML as follows:

   datatype 'a stream = empty | cons of 'a * (unit -> 'a stream);
   
   fun head (cons(a,f)) = a;
   fun tail (cons(a,f)) = f();
(we are obliged to use different names than ::, hd and tl, because the latter are reserved for the (eager) lists, which in ML are predefined). Now we can define function on streams in the usual way. For instance, the functions nats, times and facts of previous lecture can be defined as follows:
   fun nats n = cons(n, fn() => nats(n+1));
   fun times r s = cons((head r)*(head s), fn() => times (tail r) (tail s));
   fun facts () = cons(1, fn () => times (facts ()) (nats 2));
(here we need to give an argument to facts only to satisfy the ML constraint on the syntax of functions.) Now, we can define, for instance
   val s = facts();
The following are examples of interactions with ML (after giving the above definitions):
   - head(tail s);
   val it = 2 : int
   - head(tail(tail(s)));
   val it = 6 : int
   - head (tail(tail(tail(tail s))));
   val it = 120 : int

Lambda Prolog interpreters for the eager and the lazy semantics of PCF

We describe here a possible lambda Prolog implementation of the operational semantics of PCF.

Syntax

The first step is representing the (abstract) syntax of PCF. Assuming that tm is the type of the lambda Prolog terms representing PCF terms, we need to define a function
[.] : PCF -> tm
We define [.] compositionally, introducing a new constructor each time we need it, in the following way:
[n] = c n (for n integer), where c : int -> tm
[true] = tt, where tt : tm
[false] = ff, where tt : tm
[M op N] = opp [M] [N], where for op = +, opp = plus : tm -> tm -> tm, etc.
[if M then N else P] = ite [M] [N] [P], where ite : tm -> tm -> tm -> tm
[(M,N)] = pair [M] [N] [P], where pair : tm -> tm -> tm
[fst M] = fst [M], where fst : tm -> tm
[snd M] = snd [M], where fst : tm -> tm
[M N] = app [M] [N], where app : tm -> tm -> tm
[\x.M] = ab (x\ [M]), where ab : (tm ->tm) -> tm
[let x = M in N] = let [M] (abs (x\[N])), where let : tm -> (tm -> tm) -> tm
[fix M] = fix [M], where fix : tm -> tm
[x] = x (for x variable)
Notes: Below is the lambda Prolog module for representing the syntax of PCF
   module PCF_Syntax.

   %%% main syntactic category: 

   kind tm type.  % terms of PCF

   %%% constructors for representing terms of Mini-ML. 

   type c int -> tm.                             % numerical constant
   type tt, ff tm.                               % boolean constants
   type plus, minus, times, dv tm -> tm -> tm.   % numerical ops
   type equal tm.                                % comparison 
   type ite tm -> tm -> tm -> tm.                % conditional
   type pair tm -> tm -> tm .                    % pairing
   type fst, snd tm -> tm.                       % projections
   type ab (tm -> tm) -> tm.                     % abstaction
   type app tm -> tm -> tm.                      % application
   type let tm -> (tm -> tm) -> tm.              % local declaration
   type fix tm -> tm.                            % fixpoint operator

Eager semantics

We want to define a predicate eval: tm -> tm -> o so that (eval M N) holds iff M evaluates to N in the system for the eager operational semantics. Here is a possible definition.
   module PCF_eager_semantics.
   
   import PCF_Syntax.

   eval (c N) (c N).
   eval tt tt.
   eval ff ff.
   eval (plus M N) (c X) :- eval M (c Y), eval N (c Z), X is Y + Z. 
   eval (minus M N) (c X) :- eval M (c Y), eval N (c Z), X is Y - Z. 
   eval (times M N) (c X) :- eval M (c Y), eval N (c Z), X is Y * Z. 
   eval (dv M N) (c X) :- eval M (c Y), eval N (c Z), X is Y div Z. 
   eval (equal M N) tt :- eval M (c Y), eval N (c Z), Y = Z. 
   eval (equal M N) ff :- eval M (c Y), eval N (c Z), (Y < Z ; Z < Y). 
   eval (ite M N P) V :- eval M tt , eval N V.
   eval (ite M N P) V :- eval M ff , eval P V.
   eval (pair M N) (pair V W) :- eval M V, eval N W.
   eval (fst M) V :- eval M (pair V W).
   eval (snd M) W :- eval M (pair V W).
   eval (ab M) (ab M).
   eval (app M N) V :- eval M (ab P) , eval N Q , eval (P Q) V.
   eval (let M N) V :- eval M P, eval (N P) V.
   eval (fix M) V :- eval M (ab N), eval (N (fix M)) V.

Lazy semantics

This time we want eval: tm -> tm -> o to represent the lazy evaluation. Here is a possible definition.
   module PCF_eager_semantics.

   import PCF_Syntax.

   eval (c N) (c N).
   eval tt tt.
   eval ff ff.
   eval (plus M N) (c X) :- eval M (c Y), eval N (c Z), X is Y + Z. 
   eval (minus M N) (c X) :- eval M (c Y), eval N (c Z), X is Y - Z. 
   eval (times M N) (c X) :- eval M (c Y), eval N (c Z), X is Y * Z. 
   eval (dv M N) (c X) :- eval M (c Y), eval N (c Z), X is Y div Z. 
   eval (equal M N) tt :- eval M (c Y), eval N (c Z), Y = Z. 
   eval (equal M N) ff :- eval M (c Y), eval N (c Z), (Y < Z ; Z < Y). 
   eval (ite M N P) V :- eval M tt , eval N V.
   eval (ite M N P) V :- eval M ff , eval P V.
   eval (pair M N) (pair M N).
   eval (fst M) V :- eval M (pair N P), eval N V.
   eval (snd M) W :- eval M (pair N P), eval P V.
   eval (ab M) (ab M).
   eval (app M N) V :- eval M (ab P), eval (P N) V.
   eval (let M N) V :- eval M P, eval (N P) V.
   eval (fix M) V :- eval M (ab N), eval (N (fix M)) V.
Example of terms on which we can test eval:
    type test int -> tm -> o.

    %%% Test #1: term representing the factorial function:

    test 1 (fix (ab (f\ ab (x\ ite (equal x (c 0)) 
                                   (c 1) 
                                   (times x (app  f (minus x (c 1)))))))).

    %%% Test #2: term representing factorial applied to 4

    test 2 (F (c 4)) :- test 1 F.
We can for instance formulate the query
    ?- test 2 M, eval M N.
which should return the solution N = (c 24).