Relay-Version: version B 2.10 5/3/83; site utzoo.UUCP Path: utzoo!mnetor!uunet!seismo!mcvax!inria!crcge1!adams From: adams@crcge1.UUCP (Drew Adams) Newsgroups: comp.lang.misc,comp.lang.lisp Subject: Recognizing QUOTE deemed harmful to EVAL's laziness Message-ID: <2698@crcge1.UUCP> Date: Tue, 21-Jul-87 05:45:43 EDT Article-I.D.: crcge1.2698 Posted: Tue Jul 21 05:45:43 1987 Date-Received: Thu, 23-Jul-87 02:41:13 EDT Lines: 402 Keywords: LISP lazy evaluation reduction denotation quotation Xref: mnetor comp.lang.misc:560 comp.lang.lisp:344 WARNING: This is a bit long In article 444 of comp.lang.misc Robert Firth writes: ... In applicative languages, repeated evaluations of an expression always yield the same result, so we can replace delayed evaluation by true lazy evaluation, ie evaluate once only when first referenced. ... SUMMARY: I agree, but argue that this is not true of all applicative languages; in particular, it's not true of pure LISP, due to its treatment of quotation. The meaning of the term "lazy evaluation" is not universally agreed upon. Some use it to mean "call-by-need", i.e. the one-shot evaluation referred to above. Others identify it with "call-by-name" (or "normal order reduction" in the context of the lambda calculus and combinatory logic.) "True" call-by-need is now more commonly termed "fully lazy reduction" in functional programming circles. Henderson & Morris and Friedman & Wise are usually credited with the introduction of "lazy evaluation". I believe Wadsworth was the first to speak of "call-by-need" and in any case he was the first to point out the delicacies of "full laziness", although I believe the latter term is due to Hughes. The concepts of different evaluation orders are of course much older than these terms, predating the electronic computer age [Church, Curry, Rosser]. The simple argument given above by Firth bears repeating and is the basis for the assertion that "lazy evaluation and side effects don't mix" (cf. Abelson & Sussman). I would only add one change to the argument: full laziness lends itself most naturally not to applicative (functional) languages in general but, more precisely, to those respecting "*reduction* semantics". In particular, the purely functional language "pure LISP" (without side effects but with built-in quotation) does *not* respect such semantics and is thus not easily made (fully) lazy. The issue is quotation. Without its built-in recognition of quotation LISP otherwise has reduction as its operational semantics. To be "applicative" or "purely" functional a language must be "referentially transparent", meaning that the same expression, in the same definitional context, is always evaluated to the same result. Pure LISP has this property. "Reduction semantics" refers to the property of always being able to replace an expression by its result, in the same definitional context, without altering the result of the overall computation. Because of this property, an expression and its reduction may thus be said to "*denote* equivalent values". The property which Mr. Firth referred to above, of obtaining the same result no matter how many times an expression is evaluated, is implied by reduction semantics, but not, I believe, by the semantics of pure LISP. Of course, due to the possibility of side effects, in *impure* LISP, re-interpretation of even the *same* code may produce different results. Here, however, the issue is *repeated* interpretation of code -- not that original source code is interpreted more than once, but that the *result* of interpretation is then interpreted and possibly re-interpreted .... Once *reduction* of an expression is started we can assume that it proceeds all the way to some kind of an irreducible (normal) form, so that further attempts at reduction have no effect. In lazy functional languages this final form is not usually "*the* normal form" of the lambda calculus in which no redexes (reducible subexpressions) appear, but the so-called "weak head", or "lazy" normal form, in which only the outermost function application need be irreducible. In reduction-based languages intermediate forms aren't normally even available to the programmer. This is not a problem semantically; one never need worry about reduction having "gone too far", because the intermediate, as well as initial and normal forms, may all be seen as equivalent. I don't know what (pure) LISP's operational semantics should be called. If it weren't already in use we might consider the term "denotational" to characterise its *operational* semantics, since the built-in recognition of quotation provides the ability to distinguish *different levels* of representation of meaning. The LISP expression (QUOTE FOO) might be said in a sense to denote the expression (value) FOO to which it evaluates, but the two aren't equivalent; i.e. there are many contexts in which one can't be replaced by the other without changing the overall meaning and result. For example, if the value of FOO is defined to be 2 then (NUMBERP FOO) is true (T), whereas (NUMBERP (QUOTE FOO)) is false (NIL). (For simplicity, let's assume denoted values are always expressions; values of the function represented by the functor QUOTE are necessarily so.) In a reduction-based language, if A is defined as B, and B as C, reducing A gives C, and all three may be said to have equivalent meaning, as determined, or shown, by the operation of the language's equality predicate. In LISP on the other hand, if A is defined to have the value B, and B to have the value C, evaluating A gives B, not C. This behavior is due to the recognition of QUOTE. The meaning of A isn't necessarily the same as that of B, let alone that of C, as determined by LISP predicates such as EQ and EQUAL. Denotation, unlike reduction, is leveled: A may denote B which denotes C..., but we need not (indeed had better not) assume this means, e.g., that A denotes C. Thus, if A (LISP-) evaluates to B, and (independently) B evaluates to C, we can assume neither that A is equivalent to C, nor that A denotes C. (In this case, the fact that evaluation of A stops at B means that (QUOTE B) was necessarily encountered during the evaluation of A.) Having such a denotation mechanism available in a language facilitates a leveled view of things. However, it would seem that most programmer use of quotation in LISP doesn't really involve intentional semantic leveling. The most frequent uses of quotation in LISP would appear to be: * to prevent the attempted evaluation of something which is undefined, together with the ensuing error treatment * to prevent the immediate evaluation of a defined expression because the moment isn't right. The first of these is by definition unnecessary in a language "tolerant of the undefined". By this I mean that undefined expressions are simply regarded as irreducible (in normal form) and provoke no error treatment. (By "undefined expression" I mean an expression matching no definition left hand side.) Most "rewriting systems" are so tolerant; most functional languages are not, although many permit the declaration of constructions. The second common use of LISP quotation may be motivated by concerns of correctness, as well as efficiency. The correctness concern is due to the fact that replacing an expression by its value doesn't, in general, preserve semantic equivalence, as was indicated above. Besides the special treatment of quotations, this is due to the non-declarative nature of (impure) LISP. The efficiency concern is due to the "eager" (non-lazy) nature of LISP. Even if it would not be incorrect in a given situation to evaluate certain expressions, it may be advisable on efficiency grounds to postpone their evaluation, perhaps indefinitely. Reduction semantics dispense with the first of these concerns, and lazy interpretation (*normal order* reduction) may be used to alleviate the second: subexpressions of an interpreted expression are not necessarily reduced. In LISP, for example, one often places oneself at a meta-level to construct some code which is passed around and manipulated, and then at an opportune moment is executed. In a lazy language such code need not be treated as quotation: as long as its interpretation isn't needed by the functions which pass it around it won't be reduced. Even without laziness, as long as the code is underdefined little or no reduction can take place, so that code may be constructed directly without it needing to pass through a phase where it takes on the form of a construction such as a list. (A function application may be underdefined either because all its arguments are not yet present ("partial parameterization") or because no definition for it has yet been interpreted.) In other words, QUOTE is mainly used by LISP programmers to offset the eagerness of EVAL. If one weren't afraid of throwing oil on forscoming lithpian flames (this one can feel the heat already) one *might* argue that, as all computation in LISP is evaluation (in a denotational sense), the programmer is obliged to conceptually move up and down between semantic levels that are essentially artificial in a purely declarative setting. They don't correspond to her conception of the meaning of the program but rather function as an ad hoc mechanism to keep the eagerness of EVAL at bay. She in fact eventually learns to ignore or abstract from them when mentally interpreting code. LISP requires programmers to employ quotation even when it serves no apparent semantic purpose. (Such overuse would of course be greatly reduced were programmers not obliged to consider also the side effects and referential opacity of (impure) LISP.) (Cf. Wadler) Likewise, it would seem that in our daily reasoning we are rarely conscious of using more than few semantic levels at once. Nevertheless, there *are* times when a device for manipulating programmer-defined levels of denotation is useful. Implementation/ manipulation of languages is a typical example where quotation is appropriate. As such leveling is a very powerful abstraction construct, it would be desirable not to have to do without denotation in opting for reduction semantics. One would like to be able to define meta-meta-levels, and so on. Fortunately it's easy to have our cake and eat it too in a reduction-only setting by simply programming quotation/denotation *explicitly* for use just where we need it. Consider the hypothetical question "What is the denotation of ((3 - 7) + (4 / 2)) * 6 ?". A reduction interpreter is only in effect able to respond "It's the denotation of -12, whatever that may be". But suppose now that for a given application a programmer wants to consider that the true meaning of -12 is, say, the object represented by the expression OVERDRAWN. It's straightforward to define a function MEANING which will perform such an EVALuation. (Function application is represented here by juxtaposition: Sin 3.14159, not sin(3.14159)): Meaning -12 = Overdrawn Suppose now that OVERDRAWN in turn is defined (though its *meaning* is not): Overdrawn = More-debits-than-credits where the right hand side is simply an undefined term. If this is the case then the original request is reduced as follows: Meaning (((3 - 7) + (4 / 2)) * 6) ==> Meaning -12 ==> Overdrawn ==> More-debits-than-credits Of course, this result has nothing to do, a priori, with the *meaning* of the expression OVERDRAWN. The latter (which is the same as the *meaning of the meaning* of the expression -12) might be defined: Meaning More-debits-than-credits = Spendthrift Assuming, again, that the right hand side is undefined, we have: Meaning Overdrawn ==> Meaning More-debits-than-credits ==> Spendthrift Such a simple denotational mechanism makes no assumption about the meanings of constructions (undefined terms) such as, e.g., that they denote themselves. In this it is a more flexible device than that provided by LISP's EVAL. If 5 is a construction, then, although 5 *reduces* to itself (i.e. is irreducible), the MEANING of 5 is not 5 (or anything else) by default, nor are we prohibited from defining it to be 6. This is a good reminder that denotational evaluation need not be equivalence-preserving. For, while in our own eyes and the eyes of a function such as MEANING we may identify, say, 2 and 3 by defining their MEANINGs to both be, say, 7, or RHUBARB, or whatever, this equivalence is fictive as far as the interpreter is concerned. Then: ((Meaning 2) = (Meaning 3)) ==> True, whereas (2 = 3) ==> False. More importantly, it is not even operationally possible for us to assign two expressions which have the same normal form, such as (2 + 2) and 4, different meanings. The quotation mechanism itself may of course be provided quite simply by the general rule: Meaning (Quote x) = x where QUOTE is simply a constructor (undefined symbol). Then: Meaning (Quote -12) ==> -12 Note incidentally that we are of course in no way limited to a single MEANING function, but may easily define as many different denotations as we like. Likewise it might sometimes be useful to have various quoting constructors. Such constructors are in effect used to establish different denotational levels (meta, meta-meta, etc.), and having different such quoting devices would allow different meaning functions to move differently among the various levels, even with respect to the same argument expression. Similarly, a given meaning function might recognize more than one form of quotation. E.g., My-meaning (My-quote x) = x Your-meaning (Your-quote x) = x Your-meaning (My-quote x) = Nonsense! Then, for example: My-meaning (My-quote Foo) ==> Foo Your-meaning (My-quote Foo) ==> Nonsense! My-meaning (Your-quote Foo) ==> My-meaning (Your-quote Foo) [already in normal form] To recapitulate, the argument I have with (pure) LISP's EVAL (leaving aside side effects and eagerness) is not that it is denotational, but that it's recognition of quotation is omnipresent. It is as though LISP were a reduction interpreter that automatically provided a function such as MEANING, respecting quotation, with every prompt. That is, one might say that every expression FOO evaluated by LISP is first regarded as (MEANING FOO), and is then reduced. It is difficult in general to ask LISP for a simpler version of the same expression; EVAL looks for meaning behind each input. In a reduction setting on the other hand, denotational evaluation is still available, using only the mechanism of reduction, just by defining explicit denotation functions such as MEANING. References: ========== Laziness: -------- %T A Lazy Evaluator %A Peter HENDERSON %A James H. MORRIS, Jr. %B Third Annual ACM Symposium on Principles of Programming Languages (POPL) %P 95-103 %I ACM %D January 1976 %T CONS Should Not Evaluate its Arguments %A Daniel P. FRIEDMAN %A David S. WISE %J Automata, Languages and Programming: Third Int'l Coll. %P 257-284 %E S. MICHAELSON and R. MILNER %I Edinburgh University Press %D July 1976 %T Structure and Interpretation of Computer Programs %A Harold ABELSON %A Gerald Jay SUSSMAN %A Julie SUSSMAN %I The MIT Press and McGraw-Hill %C Cambridge, Massachusetts, and New York %D 1985 "Full" laziness: --------------- %T Semantics and Pragmatics of the Lambda-calculus %A Christopher Peter WADSWORTH %I Oxford University %D September 1971 %O Ph.D. Thesis %T Design and Implementation of Programming Languages %A Robert John Muir HUGHES %R Technical Monograph PRG-40 %I Oxford University Computing Laboratory, Programming Research Group %C Oxford %D July 1983, as monograph September 1984 %O Ph.D. Thesis Reduction: --------- %T The Calculi of Lambda-Conversion %A Alonzo CHURCH %J Annals Math. Studies %V 6 %I Princeton University Press %C Princeton, New Jersey %D 1941 %T Combinatory Logic %A Haskell B. CURRY %A Robert FEYS %A William CRAIG %V 1 %I North-Holland %C Amsterdam %D 1968 %T Highlights of the History of the Lambda Calculus %A J. Barkley ROSSER %J Annals of the History of Computing %V 6 %N 4 %P 337-349 %I American Federation of Information Processing Societies %D October 1984 Quotation: --------- %T A Critique of Abelson and Sussman - or - Why Calculating is Better Than Scheming %A Philip WADLER %J SIGPLAN Notices %V 22 %N 3 %P 83-94 %I ACM %D March 1987 -- Drew ADAMS, Laboratoires de Marcoussis, Centre de Recherche de la Compagnie Generale d'Electricite, Route de Nozay, 91460 MARCOUSSIS, FRANCE Tel. 64.49.11.54, adams@crcge1.cge.fr