Jean-Louis Krivine
email: krivine@pps.jussieu.fr

A call-by-name lambda-calculus machine

Introduction

We present, in this paper, a particularly simple lazy machine which runs programs written in λ-calculus. It was introduced by the present writer more than twenty years ago. It has been, since, used and implemented by several authors, but remained unpublished. In the first section, we give a rather informal, but complete, description of the machine. In the second part, definitions are formalized, which allows us to give a proof of correctness for the execution of λterms. Finally, in the third part, we build an extension for the machine, with a control instruction (a kind of call-by-name call/cc) and with continuations. This machine uses weak head reduction to execute λ-calculus, which means that the active redex must be at the very beginning of the λterm. Thus, computation stops if there is no redex at the head of the λ-term. In fact, we reduce at once a whole chain λx 1 . . . λx n . Therefore, execution also stops if there are not enough arguments. The first example of a λ-calculus machine is P. Landin's celebrated SECD-machine [START_REF] Landin | The mechanical evaluation of expressions[END_REF]. The one presented here is quite different, in particular because it uses call-by-name. This needs some explanation, since functional programming languages are, most of the time, implemented through call-by-value. Here is the reason for this choice : Starting in the sixties, a fascinating domain has been growing between logic and theoretical computer science, that we can designate as the Curry-Howard correspondence. Succinctly, this correspondence permits the transformation of a mathematical proof into a program, which is written :

in λ-calculus if the proof is intuitionistic et only uses logical axioms ; in λ-calculus extended with a control instruction, if one uses the law of excluded middle [START_REF] Griffin | A formulae-as-type notion of control[END_REF] and the axioms of Zermelo-Fraenkel set theory [START_REF] Krivine | Typed λ-calculus in classical Zermelo-Fraenkel set theory[END_REF], which is most often the case. Other instructions are necessary if one uses additional axioms, such as the Axiom of Choice [START_REF] Krivine | Dependent choice, 'quote' and the clock[END_REF].

The programs obtained in this way are indeed very complex and two important problems immediately arise : how should we execute them and what is their behaviour ? Naturally, these questions are not independent, so let us give a more precise formulation : (i) How should one execute these programs so as to obtain a meaningful behaviour ? (ii) Assuming an answer to question (i), what is the common behaviour (if any) of the programs obtained from different proofs of the same theorem ?

It is altogether surprising that there be an answer to question (i) ; it is the machine presented below. I believe that is, in itself, a strong reason for being interested in it. Let us give a very simple but illuminating example, namely the following theorem of Euclid :

There exists infinitely many prime numbers. Let us consider a proof D of this theorem, using the axioms of classical analysis, or those of classical set theory ; consider, further, the program P D extracted from this proof. One would like to have the following behaviour for P D :

wait for an integer n ; produce then a prime number p ≥ n.

That is exactly what happens when the program P D is executed by the present machine. But it's not true anymore if one uses a different execution mechanism, for instance call-by-value. In this case one gets, in general, an aberrant behaviour and no meaningful output.

This machine was thus conceived to execute programs obtained from mathematical proofs. It is an essential ingredient of the classical realizability theory developed in [START_REF] Krivine | Typed λ-calculus in classical Zermelo-Fraenkel set theory[END_REF][START_REF] Krivine | Dependent choice, 'quote' and the clock[END_REF] to extend the Curry-Howard correspondence to analysis and set theory. Thanks to the remarkable properties of weak head reduction, one can thus, inter alia, search for the specification associated with a given mathematical theorem, meaning the shared behaviour of the programs extracted from the various proofs of the theorem under consideration : this is question (ii) stated earlier. That problem is a very interesting one, it is also quite difficult and has only been solved, up to now, in very few cases, even for tautologies (cf. [START_REF] Danos | Disjunctive tautologies and synchronisation schemes[END_REF]). A further interesting side of this theory is that it illuminates, in a new way, the problem of proving programs, so very important for applications.

Description of the machine

Terms of λ-calculus are written with the notation (t)u for application of t to u. We shall also write tu if no ambiguity arise ; (. . . ((t)u 1 )u 2 . . .)u k will be also denoted by (t)u 1 . . . u k or tu 1 . . . u k . We consider three areas in the memory : the term area where are written the λ-terms to be performed, the stack and the heap. We denote by &t the address of the term t in the term area.

In the heap, we have objects of the following kinds :

-environment : a finite sequence (e, ξ 1 , . . . , ξ k ) where e is the address of an environment (in the heap), and ξ 1 , . . . , ξ k are closures. There is also an empty environment.

-closure : an ordered pair (&t, e) built with the address of a term (in the term area) and the address of an environment.

The elements of the stack are closures. Intuitively, closures are the values which λ-calculus variables take.

Execution of a term

The term t 0 to be performed is written, in "compiled form" in the term area. The "compiled form" of a term is obtained by replacing each occurrence of λx with λ and each variable occurrence with an ordered pair of integers (ν, k) (it is a variant of the de Bruijn notation [START_REF] De Bruijn | Lambda calculus notation with nameless dummies, a tool for automatic formula manipulation, with application to the Church-Rosser theorem[END_REF], see the definition below). We assume that t 0 is a closed term. Thus, the term area contains a sequence of closed terms. Nevertheless, terms may contain symbols of constant, which are performed with some predefined programs. For example :

-a constant symbol which is the name of another closed term ; the program consists in the execution of this term.

-constant symbols for programs in an input-output library.

The execution consists in constantly updating a closure (T, E) and the stack. T is the address of the current subterm (which is not closed, in general) : it is, therefore, an instruction pointer which runs along the term to be performed ; E is the current environment. At the beginning, T is the address of the first term t 0 to be performed. Since it is a closed term, E is the null pointer (which points to the empty environment). At each moment, there are three possibilities according to the term pointed by T : it may be an application (t)u, an abstraction λx t or a variable.

-Execution of (t)u. We push the closure (&u, E) on the top of the stack and we go on by performing t : thus T points now to t and E does not change.

-Execution of λx 1 . . . λx n t where t does not begin with a λ ; thus, T points to λx 1 .

A new environment (e, ξ 1 , . . . , ξ n ) is created : e is the address of E, ξ 1 , . . . , ξ n are "popped" : we take the n top entries off the stack.

We put in E the address of this new environment in the heap, and we go on by performing t : thus T points now to t.

-Execution of x (a λ-calculus variable).

We fetch as follows the value of the variable x in the environment E : indeed, it is a bound occurrence of x in the initial term t 0 . Thus, it was replaced by an ordered pair of integers <ν, k>. If ν = 0, the value we need is the k-th closure of the environment E.

If ν ≥ 1, let E 1 be the environment which has its address in E, E 2 the one which has its address in E 1 , etc. Then, the value of x is the k-th closure of E ν . This value is an ordered pair (T , E ) which we put in (T, E).

Remark.

The intuitive meaning of these rules of execution is to consider the symbols λx, (, x of λ-calculus as elementary instructions :

• "λx" is : "pop" in x and increment the instruction pointer.

• "(" is : "push" the address of the corresponding ")" and increment the instruction pointer.

• "x" is : go to the address which is contained in x.

It remains to explain how we compute the integers ν, k for each occurrence of a variable x, i.e. how we "compile" a closed λ-term t. More generally, we compute ν for an occurrence of x in an arbitrary λ-term t, and k when it is a bound occurrence in t. This is done by induction on the length of t.

If t = x, we set ν = 0. If t = uv, the occurrence of x we consider is in u (resp. v). We compute ν, and possibly k, in u (resp. v).

Let now t = λx 1 . . . λx n u with n > 0, u being a term which does not begin with a λ. If the occurrence of x we consider is free in t, we compute ν in t by computing ν in u, then adding 1. If this occurrence of x is bound in u, we compute ν and k in u. Finally, if this occurrence is free in u and bound in t, then we have x = x i . We compute ν in u, and we set k = i.

Formal definitions and correction proof

Compiled terms or λ B -terms (this notion is a variant of the de Bruijn notation) are defined as follows :

• A constant a or an ordered pair <ν, k> (k ≥ 1) of integers is a λ B -term (atomic term).

• If t, u are λ B -terms, then so is (t)u.

• If t is a λ B -term which does not begin with λ i and if n ≥ 1, then

λ n t is a λ B -term.
Let us consider, in a λ B -term t, an occurrence of a constant a or of <ν, k> (ordered pair of integers). We define, in an obvious way, the depth of this occurrence, which is the number of λ n symbols above it.

The definition is done by induction on the length of t : If there is no λ i symbol in t, the depth is 0. If t = (u)v, the occurrence we consider is either in u or in v. We compute its depth in this subterm and do not change it. If t = λ n u, we compute the depth of this occurrence in the subterm u and we add 1 to it.

An occurrence of <ν, k> in t is said to be free

(resp. bound) if its depth in t is ≤ ν (resp. > ν).
Of course, each occurrence of a constant a is free. Thus, we could write constants as ordered pairs <∞, k>.

Consider a bounded occurrence of <ν, k> in a λ B -term t ; then there is a unique λ n in t which bounds this occurrence. If k > n, we say that this occurrence of <ν, k> in t is dummy. A λ B -term without dummy bound occurrences will be called good. We can easily transform a λ Bterm into a good one : simply substitute each dummy occurrence with a (unique) new constant symbol d.

Alpha-equivalence

Let t be a closed λ-term, with constants. We define, by induction on t, its "compiled" form, which is a λ B -term denoted by

B(t) : If a is a constant, then B(a) = a ; if t = uv, then B(t) = B(u)B(v).
If t = λx 1 . . . λx n u where u does not begin with λ, consider the λ Bterm : B(u[a 1 /x 1 , . . . , a n /x n ]), where a 1 , . . . , a n are new constants. We replace in it each occurrence of a i with the ordered pair <ν, i>, where ν is the depth of this occurrence in B(u[a 1 /x 1 , . . . , a n /x n ]). We get in this way a λ B -term U and we set :

B(t) = λ n U . THEOREM 1. A λ B -term τ is good iff there exists a λ-term t such that τ = B[t].
We omit the easy proof.

The compiled form B[t] of a λ-term t is a variant of the de Bruijn notation for t. Its main property, expressed by theorem 2, is that it depends only on α-equivalence class of t. This property is not used in the following, but the simplicity of the proof below convinced me to give it here. THEOREM 2. Two closed λ-terms t, t are α-equivalent (which we denote by t α t ) if and only if

B(t) = B(t ).
The proof is done by induction on t. The result is clear if t = a or t = uv. So, we assume now that t = λx 1 . . . λx n u where u does not begin with λ. If t α t or if B(t) = B(t ), then t = λx 1 . . . λx n u where u does not begin with λ. Let a 1 , . . . , a n be new constants ; then, by definition of α-equivalence [START_REF] Krivine | Lambda-calculus, types and models[END_REF], we have :

t α t ⇔ u[a 1 /x 1 , . . . , a n /x n ] α u [a 1 /x 1 , . . . , a n /x n ].
By induction hypothesis, this is equivalent to :

B(u[a 1 /x 1 , . . . , a n /x n ]) = B(u [a 1 /x 1 , . . . , a n /x n ]). If B(u[a 1 /x 1 , . . . , a n /x n ]) = B(u [a 1 /x 1 , . . . , a n /x n ]
), we obviously have B(t) = B(t ). But conversely, we get B(u[a 1 /x 1 , . . . , a n /x n ]) from B(t), by removing the initial λ n and replacing <ν, i> with a i for every occurrence of <ν, i> the depth of which is precisely equal to ν. Therefore, we have B(u[a 1 /x 1 , . . . , a n /x n ]) = B(u [a 1 /x 1 , . . . , a n /x n ]) ⇔ B(t) = B(t ) and finally t α t ⇔ B(t) = B(t ).

Weak head reduction

Consider a λ B -term of the form (λ n t)u 1 . . . u p with p ≥ n. Then, we can carry out a weak head reduction step : we get the λ B -term t u n+1 . . . u p (or t , if n = p) ; the term t is obtained by replacing in t each free occurrence of <ν, i> with : <ν -1, i> if ν is strictly greater than the depth of this occurrence ; u i (resp. d) if ν is equal to the depth of this occurrence and i ≤ n (resp. i > n) ; d is a fixed constant, which replaces dummy bound occurrences in λ n t.

We write t u if u is obtained from t by a finite (possibly null) number of weak head reduction steps.

It is clear that the weak head reduction of a λ-term t corresponds to the weak head reduction of its compiled form B(t).

Closures, environments and stacks

We now define recursively closures and environments : ∅ is an environment (the empty environment) ; if e is an environment and φ 1 , . . . , φ n are closures (n ≥ 0), then the finite sequence (e, φ 1 , . . . , φ n ) is an environment. A closure is an ordered pair (t, e) composed with a λ B -term t and an environment e. A stack is a finite sequence π = (φ 1 , . . . , φ n ) of closures. We denote by φ.π the stack (φ, φ 1 , . . . , φ n ) obtained by "pushing" the closure φ on the top of the stack π.

Execution rules

A state of the machine is a triple (t, e, π) where t is a λ B -term, e an environment and π a stack. We now give the execution rules, by which we pass from a state (t, e, π) to the next one (t , e , π ) :

• If t = (u)v, then t = u, e = e and π = (v, e).π.

• If t = λ n u, the length of the stack π must be ≥ n, otherwise the machine stops. Thus, we have π = φ 1 . . . φ n .π , which defines π . We set t = u and e = (e, φ 1 , . . . , φ n ).

• If t = <ν, k> : let e 0 = e and let e i+1 be the environment which is the first element of e i , for i = 0, 1, . . . If e i = ∅ for an i ≤ ν, then the machine stops. Otherwise, we have e ν = (e ν+1 , (t 1 , ε 1 ), . . . , (t p , ε p )). If k ≤ p, we set t = t k , e = ε k and π = π. If k > p, then the machine stops.

The value of a closure

Given any closure φ = (t, e), we define a closed λ B -term which is denoted by φ or t[e] ; it is defined by induction on the environment e as follows :

If e = ∅, we obtain t[∅] by replacing in t each free occurrence of <ν, i> (i.e. its depth is ≤ ν) with the constant d.

If e = (ε, φ 1 , . . . , φ n ), we set t[e] = u[ε] where u is the λ B -term we obtain by replacing in t each free occurrence of <ν, i> with : <ν -1, i> if ν is strictly greater than the depth of this occurrence ; φi (resp. d) if ν is equal to the depth of this occurrence and i ≤ n (resp. i > n) ; d is a fixed constant.

Remark. We observe that t[e] is a closed λ B -term, which is obtained by replacing in t the free occurrences of <ν, i> with suitable λ B -terms. These closed λ B -terms are recursively provided by the environment e ; the constant d (for "dummy") is used as a "wild card", when the environment e does not provide anything. THEOREM 3. Let (t, e, π), (t , e , π ) be two consecutive states of the machine, with π = (φ 1 , . . . , φ m ) and π = (φ 1 , . . . , φ m ). Then, we have :

t[e] φ1 . . . φm t [e ] φ 1 . . . φ m .
Recall that the symbol denotes the weak head reduction. We shall use the notation t[e]π for t[e] φ1 . . . φm when π is the stack (φ 1 , . . . , φ m ). There are three possible cases for t :

• t = (u)v : we have t[e] = u[e]v[e], t [e ] = u[e]
(since e = e) and π = (v, e).π. Therefore t[e]π = t [e ]π .

• t = <ν, k> : let e 0 = e and e j+1 the environment which is the first element of e j , if e j = ∅. Then, by the reduction rules of the machine, we have (t , e ) = ψ k where ψ k is the k-th closure of the environment e ν = (e ν+1 , ψ 1 , . . . , ψ p ) (and k is necessarily ≤ p). Now, by definition of t[e], we have t[e] = ψk . Therefore, t[e]π = ψk π = t [e ]π , since π = π.

• t = λ n u : then we have n ≤ m and t = u, e = (e, φ 1 , . . . , φ n ), π = (φ n+1 , . . . , φ m ). We must show that (λ n u)[e] φ1 . . . φn u[(e, φ 1 , . . . , φ n )]. By the very definitions of the value of a closure and of the weak head reduction, we have u[(e, φ 1 , . . . , φ n )] = v[e], where v is obtained by one step of weak head reduction in (λ n u) φ1 . . . φn . We denote this by (λ n u) φ1 . . . φn 1 v, and we now show that (λ n u)[e] φ1 . . . φn 1 v[e] (which will give the result). We obtain v[e] by replacing, in u, the free occurrences of <ν, i> i) with φi (or d if i > n) if ν = the depth of this occurrence ; ii) with <ν -1, i> if ν > the depth of this occurrence ; and after that, we replace this occurrence of <ν -1, i> with the closed term given by the environment e. Now, we obtain (λ n u)[e] (which is closed) by the substitution (ii) on the free occurrences of <ν, i> in u such that ν > the depth of this occurrence in u. Indeed, they are exactly the free occurrences in λ n u. Then, one step of weak head reduction on (λ n u)[e] φ1 . . . φn performs the substitution (i) on the occurrences of <ν, i> in u such that ν = the depth in u of this occurrence. This shows that this step of reduction gives v[e].

This theorem shows that the machine which has been described above computes correctly in the following sense : if t at 1 . . . t k , where t is a closed λ-term and a is a constant, then the execution of B(t), from an empty environment and an empty stack, will end up in aB(t 1 ) . . . B(t k ). In particular, if t a, then the execution of B(t) will end up in a.

Control instruction and continuations

We now extend this machine with a call-by-name control instruction, and with continuations. There are two advantages : first, an obvious utility for programming ; second, in the frame of realisability theory (see the introduction), this allows the typing of programs in classical logic and no longer only in intuitionistic logic. Indeed, the type of the instruction call/cc is Peirce's law ((A → B) → A) → A (see [START_REF] Griffin | A formulae-as-type notion of control[END_REF]). As we did before, we give first an informal description of the machine, then mathematical definitions.

Description of the machine

We describe only the changes. Terms are the same but there is one more constant, which is denoted by cc. There are still three memory areas : the stack and the term area, which are the same as before, and the heap which contains objects of the following kinds :

-environment : same definition.

-closure : it is, either an ordered pair (&t, e) built with the address of a term (in the term area) and the address of an environment (in the heap) ; or the address &γ of a continuation.

-continuation : it is a sequence γ = (ξ 1 , . . . , ξ n ) of closures (the same as a stack).

Execution of a term

The execution consists in constantly updating the current closure Ξ and the stack. There are now two possible forms for the current closure : (&τ, e) (where τ is a term) or &γ (where γ is a continuation). Consider the first case : Ξ = (&τ, e). There are now four possibilities for the term τ : an application (t)u, an abstraction λx t, a variable x or the constant cc. Nothing is changed during execution in the first two cases.

-Execution of x (λ-calculus variable).

As before, we fetch the value of the variable x in the environment e, which gives a closure ξ which becomes the current closure Ξ. The stack does not change.

-Execution of cc. We pop a closure ξ which becomes the current closure Ξ. We save the current stack in a continuation γ and we push the address of γ (this address is a closure) on the top of the stack. Therefore, the stack, which was of the form (ξ, ξ 1 , . . . , ξ n ), has become (&γ, ξ 1 , . . . , ξ n ) with γ = (ξ 1 , . . . , ξ n ).

Consider now the second case, when the current closure Ξ is of the form &γ. Then, the execution consists in popping a closure ξ, which becomes the current closure and in replacing the current stack with γ.

Formal definitions λ B -terms are defined as before, with a distinguished constant, which is denoted by cc.

We define recursively the closures, the environments and the stacks (which are now also called continuations) :

∅ is an environnement (the empty environnement) ; if e is an environment and φ 1 , . . . , φ n are closures (n ≥ 0), then the finite sequence (e, φ 1 , . . . , φ n ) is an environnement. A closure is either a stack, or an ordered pair (t, e) composed with a λ B -term t and an environment e.

A stack (or continuation) is a finite sequence γ = (φ 1 , . . . , φ n ) of closures. We denote by φ.γ the stack (φ, φ 1 , . . . , φ n ) which is obtained by "pushing" the closure φ on the top of the stack γ.

Execution rules

A state of the machine is an ordered pair (φ, π) where φ is a closure and π is a stack. We give now the execution rules, by which we pass from a state (φ, π) to the next one (φ , π ) :

• If φ is a continuation (i.e. a stack), then φ is the closure which is on the top of the stack π (if π is empty, the machine stops) and π = φ.

• Else, we have φ = (t, e) and there are four possibilities for the λ B -term t :

• If t = (u)v, then φ = (u, e) and π = (v, e).π.

• If t = λ n u, then the length of the stack π must be ≥ n, otherwise the machine stops. Thus, we have π = φ 1 . . . φ n .π , which defines π . We set φ = (u, e ) with e = (e, φ 1 , . . . , φ n ).

• If t = <ν, k> : let e 0 = e and let e i+1 be the environment which is the first element of e i , for i = 0, 1, . . . If e i = ∅ for an i ≤ ν, then the machine stops. Else, we have e ν = (e ν+1 , φ 1 , . . . , φ p ). If k ≤ p, we set φ = φ k and π = π. If k > p, the machine stops.

• If t = cc, then φ is the closure which is on the top of the stack π (if π is empty, the machine stops). Thus, we have π = φ .ρ where ρ is a stack. Therefore, ρ is also a closure, which we denote by φ ρ . Then, we set π = φ ρ .ρ.

LAZYMACH_kluwer.tex; 15/09/2006; 18:48; p.2

LAZYMACH_kluwer.tex; 15/09/2006; 18:48; p.3

LAZYMACH_kluwer.tex; 15/09/2006; 18:48; p.4

LAZYMACH_kluwer.tex; 15/09/2006; 18:48; p.5

LAZYMACH_kluwer.tex; 15/09/2006; 18:48; p.6

LAZYMACH_kluwer.tex; 15/09/2006; 18:48; p.7

LAZYMACH_kluwer.tex; 15/09/2006; 18:48; p.8

LAZYMACH_kluwer.tex; 15/09/2006; 18:48; p.9

LAZYMACH_kluwer.tex; 15/09/2006; 18:48; p.10