How to be correct, lazy and efficient ?

C. Recanati

Université Paris 13, av. JB. Clément, 1. Lambdix and the semantic problems 93430, Villetaneuse, of Lisp France

This paper is an introduction to In this section the semantical defects of Lambdix, a lazy Lisp interpreter lisp1 are reviewed and shown not to exist implemented at the Research Laboratory in Lambdix. These defects are of the University of Paris XI (Laboratoire characteristic of early versions of lisp, but de Recherche en Informatique, Orsay). Lambdix was devised in the course of an they are still present in current versions investigation into the relationship between (although, of course, not all defects are the semantics of programming languages present in all versions); this is why we and their implementation; it was used to demonstrate that in the Lisp domain, think this little overview is not out of date semantic correctness is consistent with even if some of points we make are now efficiency, contrary to what has often been well-known. claimed. The first part of the paper is an overview of well-known semantic 1.1. The functional argument problem difficulties encountered by Lisp as well as an informal presentation of Lambdix; it is shown that the difficulties which Lisp Lisp was first thought of as being an encouters do not arise in Lambdix. The implementation of the lambda calculus. Its second part is about efficiency in syntax allows the definition of functions implementation models. It explains why Lambdix is better suited for lazy by means of lambda expressions, as for evaluation than previous models. The instance section ends by giving comparative execution time tables. (lambda (x y) (+ x y))

1 By 'lisp' here we mean a family of languages rather than a particular language belonging to this family.

page 1 for addition. This is a fundamental feature when the function returns the lambda of Lisp. Now serious problems arise when expression; consequently, when the these lambda expressions are used as anonymous lambda function is applied, x functional arguments - when a function recovers its previous value - which is 456 takes another function as argument, and or not defined. also when a function returns such a function as value. Example 2 $ (define apply (f x) (f x) ) Example 1 $ ( define Identity ( x ) $ ( define BuildConstFunc (x) ( apply (lambda (y) x) 2)) ( lambda(y) x )) The Identity function defined here The function BuildConstFunc is is constructed by applying a function supposed to return a function lambda of y, returning x. In Lambdix, a call to ( which returns x. This lambda function Identity 45) returns 45 but in lisp: ought to be a constant function since its argument y is not used. Then a call to ( $ ( Identity 45) (BuildConstFunc 0) 1) => 2 ought to return 0, as well as This is even more surprising since only y ( (BuildConsFunc 0) 2). This is of course seems bound to 2. The source of the the case in Lambdix, but not in Lisp trouble is the use of the name x in the where: definition of apply. Had apply been $ ((BuildConsFunc 0) 1) defined as => ** error - x not defined ** or $ (setq x 456) $ ( define apply (f z) $ ((BuildConsFunc 0) 1) (f z) ) => 456 the problem would have disappeared. Here The reason for this surprising it is not because the stack has been popped answer is the following. In most lisps too early that the binding fails, but environments (bindings between names because an intermediate binding has been and values) are represented in a stack inserted: x is first bound to 45; then the which is popped when the execution of the evaluation of apply inserts the binding of function terminates. The binding of x to 0 x to 2. The lambda is evaluated with y in the application of BuildConsFunc is lost

page 2 bound to 2 and it returns x - which is now part of the benefits of functional bound to 2. programming is lost. Because of these functional argument problems, Lisp is not a true 1.2. Lexical vs dynamic scope second order functional language1, and

The foregoing examples show that the

names of formal parameters are of major 1 Some lisps proposed a function to solve importance in Lisp. This feature is due to the functional argument problem. This the type of variable binding used in Lisp, function, sometimes called closure, takes called 'most recent binding' (MRB, for two arguments - a list of formal short). MRB was perhaps, as Gowan has parameters and an expression - and returns wittily said, the 'most recent error' the application of a lambda expression. ([GOW72]). Originally motivated by the For instance, the value returned by the technical advantages of the closure function applied to the list '(x) implementation, dynamic scoping of the and an expression Expr - in an type illustrated by MRB has disastrous environment where x is bound to 2 - is consequences for the safety of the given by : language. How can you trust a language $ ( closure '(x) 'Expr ) when x =2 in which the names of formal parameters => ((lambda (x) Expr) '2 ) must be taken into account?

The use of dynamic scoping in Although this can punctually solve the Lisp conflicts with the original model problem, many critics can be made to this introduced by Church, which was at the solution. First, it is not very efficient source of Lisp. In lambda calculus, a rule because it adds function calls and requires known as the ß-rule allows the reduction the evaluation of all the parameters to be of terms along the following pattern: saved. Moreover, it is an ad hoc solution.

It requires the programmer to know when ( ( λ x . expr ) val ) the closure function is necessary since → expr [ x ← val ] the call to the closure function must be explicit. Furthermore, lisp distinguishing between an f-value and a -value, some particular attention must be given on the way the interpreter pass the arguments. value interpretation and this additional call This requires the use of a special function must also be explicit. (called funcall ) to force the functional

page 3 The ß-rule takes as input the application to allows the use of functional arguments and a value val of a lambda expression with is therefore a true second order language. formal parameter x and yields as output the same expression in which all tokens of 1.3. Evaluation order x have been replaced by val. The substitution is supposed to be determined 1.3.1. Call by need and call by value by the inner lambda binding in the (program) text, i.e. lexically. In lambda calculus, a term t defined by In most cases, dynamic scope and lexical scope yield the same result, but it (λ x y . x) A ((λ u . u u) (λ u . u u)) is very easy to construct cases with reduces to A because the first reduction diverging answers1 . This is why - like (the substitution of A to x) yields a term, λ many functional languages today y . A, which always reduces to A because (Common Lisp, Scheme, ML, etc.) - y does not occur in the core of this lambda Lambdix consistently uses lexical scope expression. But in Lisp the interpreter for formal parameters. Lambdix also computes the values of the arguments before the core of the function. This strategy of parameter evaluation is known 1 For instance the term t defined by as call by value. Since in the evaluation of t ≡ ( λ x . ( λ y . ( λ x . y) B ) x) A t all arguments are calculated first, the would reduced to A by ß-reduction calculation of the second argument - the t → ( λ y . ( λ x . y) B ) A) term (( λ u . u u) ( λ u . u u)) - generates → ( λ x . A) B ) an infinite loop: → A y ≡ (( λ u . u u) ( λ u . u u)) while it would reduced to B in the → (( λ u . u u) ( λ u . u u)) dynamic model: → (( λ u . u u) ( λ u . u u)) ( λ x . ( λ y . ( λ x . y) B ) x) A: → ... Stack ( λ y . ( λ x . y) B ) x) [ x – A ] In the framework of the lambda ( λ x . y) B ) [ x – A ][ y – x ] calculus, call by value can be understood y [ x – A ][ y – x ][ x – B ] as a strategy governing the order in which B the ß-reductions are performed. This A formal demonstration of the non strategy guarantees that if there is a unique equivalence of the two models can be solution, the calculus will converge on it. found in A. Eick and E. Fehr [EIC85]. Unfortunately it also guarantees that the

page 4 calculus will never return if there is also arguments and if the latter were calculated an infinite derivation. Thus for terms only when necessary. Such a strategy of having both a finite and an infinite evaluation is known as call by need. For derivation, this strategy guarantees that the instance, the function f defined by finite derivation will not be found. Hence it is not a winning strategy. The winning ( de f (x y) strategy of the lambda-calculus requires (if (< x 0) that the leftmost inner term be reduced 1 first. If there is a finite solution, the (f (- x 1) (f x y)))) calculus will converge on it (the will never terminate from a call to (f 1 2) confluence property guarantees the unicity if the interpreter conforms to the call by of the finite solution). This strategy value strategy, but it returns 1 if the corresponds to call by need. interpreter conforms go the other strategy Functions in Lisp are strict1, (call by need ). because of the strategy of the interpreter The strategic choice made by Lisp (call by value). But there is a Lisp is not necessarily objectionable on function which is not strict: the semantic grounds, because strictness can conditional test. By definition, the if be seen as a positive feature; moreover, it function does not calculate all its has the advantage of clarity. But it entails arguments: the computation of the second a loss of expressive power, because the set and the third argument is determined by of defined functions is smaller than the set the result of the computation of the first. of convergent terms of lambda calculus. Consequently, nonstrict functions could be The reason why Lisp has made this choice constructed if the core of the functions is again efficiency: more often than not, was evaluated before the values of the implementations of call by need are utterly inefficient. 1 A function is strict if, when applied to an undetermined value, the result is undetermined. This property is usually written by the equation F ( ⊥ ) = ⊥ where ⊥ denotes an undetermined value. In case of several arguments, a function is strict when: F ( ... , ⊥ , ... ) = ⊥

page 5 functions we are talking about. Laziness

certainly decreases efficiency in 1.3.2. Lazy evaluation connection with some functions, but it

yields fascinating results in connection An interpreter conforming to the call by with other functions. Well exploited, lazy need strategy is called a lazy interpreter. evaluation becomes very efficient in data There are various degrees of laziness, oriented algorithms, for instance in expert however. Pure laziness corresponds to the systems or prolog interpreters. situation in which the only arguments that A lazy cons allows the are evaluated are the arguments of the manipulation of potentially infinite lists printing functions. Such a form of laziness called streams. In Lambdix, we can is very inefficient and "lazy evaluation" define1 an infinite sequence of 1 by: generally denotes the following pattern: $ ( de x (cons 1 x)) ◊ call by need for user defined and nevertheless have functions $ (cadr x) ◊ delayed evaluation for the function = 1 cons An infinite list of integers can be also ◊ strict primitive functions defined as a function from: (+, -, etc.) stand strict

$ ( de (from x) It is the second point which makes the real (cons x (from (+ x 1)))) difference between simple call by need for user defined functions and lazy The Lambdix interpreter will have no evaluation. The introduction of a lazy problem with constructor on lists sometimes greatly improves efficiency. Lists are the most $ (print (cadr (from 2))) important structures in Lisp and lazy = 3 evaluation offers new ways of handling them. Though laziness is a richer model, 1 This is a recursive definition - which is it has always been considered less distinct from the traditional setq of lisp: efficient than the other evaluation patterns. $ (setq x 2) But is it really less efficient? This question $ (setq x (cons 1 x)) cannot be answered in a straighforward $ (cadr x) manner, for it all depends on which = 2

page 6 Another difference between Lisp and while Lisp gets trapped into an infinite Lambdix is that Lambdix distinguishes loop: between program and text. Nevertheless,

Lambdix provides two operators for an = 1 2 3 4 5 6 7 8 9 10 .... explicit conversion between text and Infinite structures can be very pleasant and program. This makes it possible for efficient in many programs. For instance programs to 'modify their own text' during in numeric application, lazy evaluation is execution. well suited for computing with formal Primitive objects of Lambdix are series. The use of infinite structures can typed (numbers, strings, characters, lists, also simplify algorithms. It can suppress booleans, primitive arithmetic operations, tests and makes algorithms shorter. primitive boolean operations, etc.). But For instance, it suppresses the need for there are two levels of language: the level iterators in unification programs. of the program text and that of its Stream processing is essential in semantic interpretation (i.e. its value). In some simulation programs. It provides an Lambdix quote does not indicate the alternative to programming with absence of evaluation, as it does in Lisp. assignments. The FlipFlop RS gives an Quote performs an operation which interesting example of this feature: converts any piece of an already computed part of the program into a list. Lists in $ (de (FlipFlop R S) Lambdix are constant values; they are not (let ( (de Q (NAnd S QBAR)) interpreted as forms. A list is a piece of (de QBAR (NAnd R Q))) text which remains constant until the (cons Q QBAR))) operation excla is applied. Excla is the dual of quote. When applied to a list, the Note that the definitions of Q and QBAR list is interpreted in the program text as if are mutually recursive and that the NAnd the corresponding piece of text had been introduced here will be an operator on written there. For instance, the function streams. mapfun which constructs the list of the applications of a function f to all the elements of a list l can be written as1: 1.4. Reflexivity in Lisp

1 An obvious advantage of this definition is that it is perfectly general; there is no need for a distinction between f-subr, f-

page 7 be defined. Top level names in Lisp are $ (de (mapfun f l) mutually defined2. (if (nullist l) () Nevertheless Lisp provides mutual (cons ( ! (cons f (car l))) definitions only at top level. In Lisp the (mapfun f (cdr l)))))) expression $ (mapfun + '((1 2) (2 3) (3 4))) = ( 3 5 7 ) (let ( (var1 expr1) ... (varN exprN) ) Here the characters ! and ' stand for the body ) operators excla and quote respectively. is equivalent to the application 1.5. Naming variables ( (lambda (var1 ... varN) body ) A fundamental difference between Lisp expr1 ... exprN ) and the lambda calculus is that in Lambdix function call is not the only way ExprN are calculated in the calling of binding names to values1. Most lisps environment. Thus cross references cannot have at least three other binding be made without additional functional mechanisms: affectation (setq ), function arguments. Since Lisp has problems with definition (introduced by second order functions, proper recursive de ) and local definitions (introduced by definitions cannot be really introduced at let ). this particular level. In Lambdix a recursive let has

been introduced in order to make local 1.5.1. Naming functions

Names introduced by de allow a term to 2 This is not a special property of lisp. In refer to itself in its own definition in such the lambda calculus, the fixed point a way that self applicative functions can operator allows self reference and then, since function can be pass as arguments, is is possible to write cross referenced terms. expr, or macro functions as possible In fact, it is lisp which offers restricted argument values. possibilities, since functions cannot be 1 Without this alternative, the choice of passed as arguments (then cross references dynamic scoping would really be cannot be done outside the top level meaningless definitions).

page 8 function definitions mutual like top-level the terms Q and QBAR must be mutually definitions. The distinction between defined. function value and cell value has also been removed from Lambdix. In Lambdix a 1.5.2. Naming stores: the assignment term has a unique value. Thus thede problem function can be used for setting any value: Names in Lisp are not used merely as $ (de x 3) tools for referencing argument values or = 3 functions. The basic concept in Lisp is not $ (de (f x) (+ x 1)) that of function but that of location. A = f variable is viewed as a name for a location at which a value can be stored. The set of Terms introduced by de have mutual all bindings at some point in a program is references within their own lexical level of known as the environment at this point. definition. The top-level is a special case Definitions, lambda expressions and let because top level definitions can be added expressions are viewed as mechanisms at any time whereas at other levels which create new locations and bind definitions come first. Levels can be variables to those locations. Thus formal imbricated. A reference to a name is first parameters are not only viewed as looked up at the same level. Parameter potential values but also as stores. In this names have priority over local definitions section we shall consider the problems when they conflict at the same lexical related to the setq instruction. level1. First note that setq is a sequential A recursive let is very useful in a instruction which must be put inside an functional language because even if it is enclosing control instruction as progn, possible to handle mutual recursion by while, etc. This means that function call is means of second order functions, this is not the only way of controlling data flow. quite inefficient and not very clear. Some This new control level has nothing to do problems cannot be dealt with without with the lambda calculus - which was our mutual recursion. For instance in the guide up to now. preceding definition of the FlipFlop RS, The benefit of assignment is efficiency. What is lost is, once again, semantic clarity. The introduction of 1 If not found as parameter or local stores in the semantic model decreases the definition, the reference is searched in the level of abstraction. For instance, if you ancestors.

page 9 compare the recursive Fibonacci function arguments of functions are evaluated does definition and the iterative one - based on not count. For instance, in the assignments - it is obvious that the interpretation of: functional definition is very close to the mathematical specification while the other $ (f (setq x 5) (setq x 6)) requires a proof. In Lisp, the top level is an implicit the order of evaluation must be taken into progn in which definitions occur. account. Note that if a setq instruction is Definitions are very similar to affectations performed within the body of g, we would since they change the values of variables. have the same problem with Is it possible to introduce a setq instruction without radically changing the $ (f (g 5) (g 7)) language? One way of doing so would be to accept the use of setq in the body of a In traditional lisps, the problem is solved let while adding an implicit progn in the because the order of evaluation is core of the let. We could then perform perfectly determined. In parallel lisps the setq instructions on local variables evaluation order is arbitrary (depending on introduced by let . This move would not processors allocation). Thus we must significally change the language since the ascertain that in such languages let in question would only mimic the top- assignments on globals are used only to level. guide the calculation (for instance to To perform assignments on formal synchronize processes) rather than to parameters, we might add sequentiality contribute to it1. through an explicit (or implicit) control In a lazy language, the situation is instruction in the body of functions. This intermediate. Shared variables are used by would obscure the formal specification in only one function at a time and the order embedding a new control level. of evaluation is determined at run time. Nevertheless, if parameters are passed by Then no special problem of concurrent value, the meaning of the function being defined could not be too much alterated. 1 This is not so, however, if assignments on Global variables are effectively shared free variables are concerned. and their content can be override by any If we accept assignment on global processes at any time. Hence some more variables, we will loose an interesting global convention on the way processes property: the fact that the order in which may affect these shared variables must be specified.

page 10 access arises as with parallelism but the The original model of Lisp 1.5 used of global stores must also be implementation used an access variable carefully specified since the order of mechanism called deep binding. This evaluation may vary from one execution model was very efficient with respect to to another. the switching of environments created by Another annoying effect of global function calls, but also fundamentally assignments is that we could obscure the inadequate to solve the meaning of the function being defined by and totally inappropriate for lazy setting a new value to the name introduced evaluation. In this model, the cost of a within the body of its own definition1. For variable access is proportional to the all these reasons, setq on global variables distance (in the of dynamic has not been allowed in standard environments) between the current Lambdix. environment and the one where the variable has been bound. Then the cost of a variable access is not bound: the access to a global variable from a recursive call 2. Implementation requires a time proportional to the

recursion depth. The main originality of Lambdix is its The MacLisp interpreter has solved implementation model. This section shows this problem by means of a new model that this model can bear comparison with called shallow binding. This new model other models on call by value, and that it was used in most Lisp implementations is far better suited for lazy evaluation. thereafter. In this model, each variable has

a corresponding cell containing its value 2.1. Previous implementation models in any environment. Therefore in any environment the access to a value is direct and constant. Thus variable accesses are very efficient. 1 For instance Nevertheless on function call, the (de (f x) old values of the argument list must be .... saved into a stack and the new values (f (setq f ...)) ... ) stored in the cells so as to reflect the To forbid setq on the term being defined bindings introduced by the function call. would be illusory since the meaning of Similarly when coming back to the term relies anyway on the meaning of all previous environment, the content of the other variables defined at the same level.

page 11 cells corresponding to the argument list What makes an implementation must be exchanged with the corresponding efficient is the cost of variable access and stacked values. This makes the the cost of environment switching. In binding/unbinding process much more traditional lisp, the cost of environment expensive than in the deep binding model. switching was not too important because it So an unexpected consequence of this only concerned neighbour environments binding model is that context switching is and could be handled by a stack very expensive. mechanism. The original shallow binding Conversely, in second order model was not designed to solve the languages (allowing functional arguments) funarg problem. However Interlisp-10 has as well as in lazy languages the cost of implemented a solution maintaining a tree environment switching is very important. of environments instead of a stack. In both cases, context switching can Context switching is done by repeating the frequently arise between two distant binding/unbinding process along the path environments. from the current environment to the future In a lazy language, context one. A common ancestor must be first switches are numerous. This is so because determined on the two paths from these the request for the calculation of an environments to the root and then half of argument occurs within the body of the the path must be gone through while unbinding the variables, and the second part while rebinding them up to the target way from the root to this environment and environment. Here the cost of context on each node, proceed to the reversal of switching is proportional to the bindings the pointer and to the exchange of binding between the source environment and the values of the environment with the content target one. This means that both the of their cells. At the end of this process, number of function calls and the number all identifiers have the correct values in of arguments are to be taken into account1. the cells. This model is known as rerooting and an inductive proof of its algorithm is given in [BAC78]. Although 1 Baker proposed an alternate solution also this method does not require the search of maintaining a tree of environment. In its a common ancestor in the dynamic tree of solution, the tree is an inverted tree, where environments, context switching is still the root is always the current environment. proportional to the bindings from the To switch from the current environment to current environment to the target one and another one, one must follow the unique consequently, still very expensive.

page 12 function, whose environment is by substitution1 corresponds to a partial definition different from that of the compilation of lexical references as function call. Therefore, each time a illustrated by figure 1. parameter must be evaluated, a context switching must be performed. f If (revised) shallow binding is the most efficient of the classical x y (+ x y) implementation models, it is not very good with respect to laziness. This is so because the cost of a switch between two fig. 1 environments is not limited by a constant but is proportional to the bindings between Furthermore, any internal lambda the environments in the dynamic tree. structure contains a pointer to its lexical 2.2. Lambdix implementation model parent structure, and this substitution is performed at an arbitrary level of 2.2.1. Variable access definition. For instance, the function double-incr defined by the following top- As in the shallow binding schema, each level definitions parameter has a cell. But a cell is not attributed to a name, but rather to a formal $ ( de (double-incr x) parameter. The binding of lexical (twice (incr x)))) parameter is known in advance by lexical $ ( de (twice f) analysis. A reference to a parameter in the core of a function is a direct reference to 1 This implementation of the ß-reduction the corresponding parameter cell. The puts Lambdix near the implementation implementation uses internal structures prompted by the De Bruinj notation (like containing formal parameter cells and the Automath) - where the occurrences of body of the lambda expression. A textual variables were replaced by the level of substitution is performed by the function their binding. But contrary to the De read of the interpreter on the body of the Bruinj model, Lambdix takes care of the function. All names are replaced by the names of the variables. This information corresponding parameter cells. This stands accessible for meta computation. (this allows a certain degree of dynamicity as shown by use of the QUOTE and EXCLA operators).

page 13 (lambda (x) (f (f x))))) by changing only dynamic pointers $ ( de (incr x) corresponding to function calls. In (lambda (y) (+ y x))) particular this makes the cost of environment switching independent of the will be represented by the internal number of parameters introduced by structures of figure 2: function calls. double-incr dynamic BLOCK f pointer x ( ( )) 5

incr 6 twice x y (+ x y)

f ! x !

y x ( ( )) (+ ) fig. 3

fig. 2 2.2.2. Representation of environments

In the same way, local definitions are The dynamic pointers to the blocks are parsed and lexical references to them are used to access values and to represent replaced by direct pointers to analogous environments1. Environments are internal structures. We shall not detail these cases here. In fact, the cell parameters do not directly contain the values. A pointer to a 1 The concept of environment generally block is attributed to each function call varies from one implementation to the and the called values are kept into it. Then other. Traditionally environments have the cells give access to their corresponding been represented as association list - i.e. as values by means of pointers to the block functions mapping Identificators to (called the dynamic pointers of the Values: internal lambda structures) and offsets sigma: (Id -> Val) (into the block). This scheme is not as fast What we call environment in this paper as pure shallow binding but in this frame corresponds to the same notion, while in variable access is also constant and very [REC86], Lambdix environments were efficient. This indirection is interesting introduced by a recursive equation: because it induces environment switching ENV = Id -> ( ENV -> Val)

page 14 transformed by function calls and function occurs (G2). From this point G calls H returns. This provides an order on the (H2) which calls G again (G3). environments - an order that is usually called 'dynamic'. Athough it is distinct P1 from the lexical ordering of the definitions, they are related. Suppose the H1 H2 functions P, H and G to be lexically organized with P at the top, H local to P G1 G2 G3 and G local to H. The lambda-structures will reflect this lexical organization by fig. 5 something like fig.4: From this last call to G, one must access P H G the parameters of the call (G3) and those of its lexical ancestors (H2 and P1). This x y body y z body t body block list (G3,H2,P1) combined with the fig. 4 lambda structures G, H, P suffices for representing the variable bindings required Now a call to H always appears within a during the execution of G3. To install or call to P. The same thing holds for G and reinstall this particular environment later, H. The dynamic environment one has only to make the dynamic pointers corresponding to a call to G will be of the lambda structures G, H and P point defined by the arguments values of this to the value blocks G3, H2 and P1 call and those corresponding to its respectively. This is illustrated by figure 6. ancestors. In figure 6, the dynamic ordering of the first three calls was: P P1 calling H calling G . Then this first call to G (G1) terminates and a new call to G H1 H2

These two views are not really opposed; G1 G2 G3 the second one fit better one of the aspects P H G of the implementation model: in a given environment, names give access to x y body y z body t body function from environments to values and a certain calculation must be performed fig. 6 before accessing a value.

page 15 closures given as functional argument) 2.2.3. Cost of environment switching will also be limited because it will stop as soon as an ancestor pointer is correctly set In Lambdix implementation, a functional - which means that in the worst case, it value is represented by a lambda structure requires the comparison of all dynamic and a block corresponding to its pointers to the top-level. But here, the environment of definition. This number of tests and assignments involved environment corresponds to an is limited by the lexical level of the environment of call of its direct ancestor. definition. It is not a dynamic depth that is The tree of dynamic environment blocks is involved in this process. It is a lexical maintained by associating each block with depth, which corresponds to the number of its dynamic (lexical) father block. One can levels introduced by let - which usually then find all dynamic ancestors pointers reduces to 1. from one block. What is important is that the cost To install an environment of of the installation of an environment does definition on the lambda structures, all not depend on the current environment ancestor dynamic pointers must be set in since it is limited by a value that is the lambda structures to their independent. It does not matter from corresponding blocks in the dynamic tree. which environment the interpreter comes, To do this, one must simply compare the but how deep this environment is in the present values of these dynamic pointers lexical sense. Furthermore, if the with those given by the tree of calling environment of definition is the same as environments. This calculation can be the previous environment (as for instance shortened by the convention that if a in recursive call) environment switching is pointer is correctly set, all the ancestors reduced to a test. will also be. This property can be easily implemented: it only requires that the previous environment be restored when a In the general case, the cost of function returns. In this way the cost of environment switching does not depend environment switching is limited to one on the number of formal parameters, as in assignment and one test when a function theshallow binding schema. Thus, call occurs within the same branch of the functions of multiple arguments are not lexical tree. disadvantaged. Now the switching from one The combination of these two environment to an arbitrary other (case of characteristics — constant variable access

page 16 and environment switching independent of implementation language, to the the current environment — makes our programmer's ability, or to the model model well suited to lazy evaluation itself. because the computation can be postponed To test our implementation model, without too drastic a supplementary cost. we have built two Lambdix interpreters: the first supporting only call by value and

the second performing only call by need. 2.3. Execution timetables Both interpreters are built on the same

implementation schema. In this section we give some execution timetables, although everyone knows that 2.3.1. Performances on call by value such results cannot not be taken too seriously. We compare Lambdix with lisps that are This is the case, first of all, the most used at the L.R.I.: last Lisp because languages have different (called lal ), Frantz Lisp and Lelisp. semantics. This means that some functions These lisps all suffer from the semantical defined in one language may be not defects listed in the first section2. defined in another. This situation arises for Lambdix both with second order functions and lazy functions manipulating infinite lists1. It is nevertheless possible to 2 Originally, a team has developed compare execution times of those software in Frantz Lisp because it was functions that can be defined in both used in the industry and because it was the languages. only one to have a . Secondly, if we want to compare Unfortunately, the interpreter was very two models for the implementation of slow because its implementation was environments, we should compare based on the deep binding model. Thus interpreters written in the same Last lisp has been designed to be an implementation language and as far as efficient interpreter compatible with possible by the same programmer; Frantz Lisp. Lelisp has been chosen by otherwise, we will not know if the results another team for the efficiency of its are due to the efficiency of the interpreter and its good library (which by the way contains a closure function). 1 We only have compared the very same implementation is based on the shallow programs. We did not take advantage of binding schema. possible simplification of lazy program.

page 17 Last Lisp was written in C at the Even if it is never the best (on call L.R.I. in 1986 by a very good by value ), our interpreter can bear programmer, Patrick Amar, and the comparison with its two rivals. It gives interpreter is optimized by assembly code better results than last Lisp on functions insertions. The LeLisp interpreter was using lists and is close to LeLisp on directly written in Assembler. Given that recursive functions manipulating numbers. our implementation is presumably less What is shown by these results is clearly optimized than those of its two that the Lambdix implementation model competitors, the results1 shown in the can compete with the shallow binding following table look very good for the model. Lambdix environment model: Comparison with the Frantz Lisp interpreter and compiler (fig. 8) illustrates Prog lal Lambdix lelisp two other points: Tak 38.5 42.5 40 Fib 8.8 13.8 13.8 ◊ deep binding is not an efficient model LComp 15.5 11.1 9.1 ◊ good interpreters can compete with Sieve 9.1 8.1 6.5 : fig. 7

The Fibonacci test (Fib) is executed with a Prog best Frantz Lisp call to (Fib 20). The well known Tak interp. comp. function is tested with a call to (Tak 18 12 Tak 38.5 overfl. 37 6). The last two tests are small programs Fib 8.8 108 6 containing recursive calls to functions LComp 9.1 overfl. 10 which manipulate lists. LComp is a Sieve 6.5 overfl. 5.5 program which compares trees leaf by leaf fig. 8 (same fringe problem). Sieve constructs the list of the first 400 prime numbers with the traditional algorithm of Eratosthenes. 2.3.2. Efficiency vs laziness

1 The experimentation has been done on a We have built two Lambdix interpreters: VAX 750, and time is expressed in the first one supporting only call by value second. To have the equivalent order on a and the second one performing only call Sun3.5 you must divided these numbers by need. The comparison of these two by a factor 2. interpreters is surely very instructive

page 18 because the two implementations both Lazy evaluation gives better results share the same implementation language, because the sum is computed only on the the same model (adapted for lazy first elements. evaluation) and the same programmer. As shown by this table, the bad Figure 9 gives a comparison between cases of lazy evaluation are between 10% Lambdix with call by need and Lambdix and 25% slower than traditional call by with call by value. We have not shown the need while the good cases are 95% better. performances of other lisps because they The worst case is that of the Tak function can be found in the previous tables and are which has three arguments and calculates in any case of the same order as Lambdix them separately - each time from another with call by value. environment. But such cases are not very frequent and the price of laziness can be Lambdix % of evaluated in our model as a 15% loss in Prog by val by need diff. efficiency with strict functions. Such a Fib 13.8 15.7 - 12 result is very important and could not have Fib2 19.5 21.7 - 10 been obtained with other models because Tak 42.5 57 - 25 of the cost of environment switching. Comp 11.1 0.7 + 94 Sieve 8.1 9.5 - 15 LSum 16.1 0.03 + 99

fig. 9

The programs tested in this table are the same as the previous ones except for Fib2 and LSum. Fib2 is a modified Fibonacci function which takes three arguments1. BIBLIOGRAPHY LSum calculates the sum of the terms of two lists generated by the Sieve program. [1] J. ALLEN, Anatomy of Lisp, McGraw-Hill Computer Science series, 1978. 1 Other lisps were very sensible to these [2] H.G. BAKER, "Shallow binding in additional arguments and gave all a result Lisp 1.5", CACM, vol 21, Nb 7, July around 20. Note that this is not the case 1978. with the Lambdix model which is less sensible to parameter addition.

page 19 [3] H.G. BAKER, "List processing in real Symposia in applied mathematics, vol 19, time on a serial computer", CACM, vol 1967. 21, Nb 4, April 1978. [14] M.P. GEORGEFF & S.F. BODNAR, [4] BARENDREGT, The Lambda "A simple and efficient implementation of Calculus, North Holland Publ. Comp., higher-order functions in LISP", Report 1981. N° CSLI-84-19, Dec. 1984. [5] D.G. BOBROW & B. WEGBREIT, [15] Mc GOWAN, "The 'most recent' "A model and stack implementation of error: its causes and correction", multiple environments", CACM, vol 16, SIGPLAN Notices, vol 7, Nb 1, 1972. Nb 10, Oct. 1973. [16] P. GREUSSAY, Contribution à la [6] N.G. de BRUIJN, "Lambda-calculus définition interprétative et à with nameless dummies, a tool for l'implémentation des lambda-langages, automatic formula manipulation", Indag Thèse de Doctorat d'Etat, N° 78-2, Nov. Math. 34, 1972. 1977. [7] G. BURN, C. HANKIN & S. [17] R. MILNER, "Logic for computable ABRAMSKY, "The theory of Strictness functions, Description of a machine Ananlysis for Higher order functions", implementation", Standford A.I. Lab. Dept. of computer Science, Imperial Memo 169, 1972. college, London, June 1985. [18] J. MOSES, "The function of [8] J. CHAILLOUX, Le modèle VLISP: FUNCTION in LISP", SIGSAM bulletin, description, implémentation et évaluation July 1970. Thèse de l'Université de Pierre et Marie [19] G. PLOTKIN, "The Category of Curie, Paris 1980. complete partial orders: a tool for making [9] J. CHAILLOUX, Le_Lisp de l'INRIA, meanings", Summer School on foundation Manuel de Réference, Février 1984. of Artificial Intelligence and Computer [10] P. COINTE, Fermetures dans les Science, Pisa 19-30 June 1978. lambda-interprètes: application aux [20] T.W. PRATT, Programming langages LISP, PLASMA et , Languages: design and implementation. Thèse de 3ème cycle, 82-11, Mars 1982. Printice-Hall, 1975. [11] H. CURRY & R. FEYS, [21] C. RECANATI, Lambdix: un Combinatory Logic, North-Holland, 1958. interprète LISP à liaison lexicale et [12] EICK & E. FEHR, "Inconsistencies évaluation paresseuse, Thèse de 3ème of pure LISP", Springer LNCS N° 145. cycle de l'Université de Paris-Sud, N° [13] R.W. FLOYD, "Assigning meanings 4072, Dec. 1986. to programs", Proc. Amer. Math. Soc.

page 20 [22] R. SETHI, "Semantics of computer programs: Overview of language definition methods", Sept. 1977. [23] D. SCOTT & C. STRACHEY, "Towards a formal semantics", Formal language description languages for computer programming, Steel. et al. eds, North-Holland, Amsterdam 1966. [24] B.C. SMITH, "Reflection and semantics in LISP", Report N° CSLI 84 8, Dec. 1984. [26] G.L. STEELE Jr, "Macaroni is better than spaghetti", SIGPLAN Notices, vol 12, Nb 8, Aug. 1977. [27] G.L. STEELE Jr & G.J. SUSMAN, The Art of the Interpreter, AI Memo Nb 453, 1978. [28] J. STOY, "Some mathematical aspects of functional programming", Functional Programming and its applications, ed Darlington et al., CUP 1982. [29] J. STOY, Denotational Semantics - The Scott-Strachey Approach to Theory, MIT Press, 1977. [30] R.D. TENNENT, "The denotational semantics of programming languages", CACM vol. 19, N°8, Aug. 1976.

page 21