5 ms·
That's a common misconception that Lisp macros are mostly used to 'delay' evaluation. What Smalltalk calls 'blocks' are just (anonymous) functions in Lisp. Boo
by lispm 8y ago
That's a common misconception that Lisp macros are mostly used to 'delay' evaluation.
What Smalltalk calls 'blocks' are just (anonymous) functions in Lisp. Books like SICP explain in detail how to use that for delayed evaluation in Lisp/Scheme:
https://mitpress.mit.edu/sites/default/files/sicp/full-text/book/book-Z-H-24.html#%_sec_3.5 https://mitpress.mit.edu/sites/default/files/sicp/full-text/...
- i_feel_great 8y agoSmalltalk blocks are anonymous functions sure, but the evaluation is delayed - you have to pass the message #value (and its variants) to it. This not the case in Scheme and maybe Lisps, where evaluation is eager. Passing Smalltalk blocks and Scheme/Lisp lambdas into functions have different behaviours.
- lispm 8y agoThat doesn't matter. One can write IF as a function taking functions as THEN and ELSE forms: CL-USER 9 > (defun my-if (test then else) (if test (funcall then) (funcall else))) MY-IF CL-USER 10 > (let ((a 10)) (my-if (> a 5) (lambda () (print 'hello)) (lambda () (print 'world))) 'done) HELLO DONE The lambdas you see above serve the same purpose as blocks in Smalltalk.
- skybrian 8y agoIt works, but the syntax isn't nice enough to use as-is, so in practice you need a macro. The innovation here is using better syntax for anonymous functions to make such things practical in languages without macros.
- lispm 8y agoIf you think brackets are the core innovation of Smalltalk to make lambdas practical, then Lisp has no problem providing it, too: CL-USER 19 > (defun bracket-reader (stream char) (declare (ignore char)) `(lambda () ,@(read-delimited-list #\] stream t))) BRACKET-READER CL-USER 20 > (set-macro-character #\[ #'bracket-reader) T CL-USER 21 > (let ((a 10)) (my-if (> a 5) [(print 'hello)] [(print 'world)]) 'done) HELLO DONE With a bit more effort, we could also parse a parameter list. But Lisp does not go the route of making IF a function, because it typically provides three different types of language expressions and IF then is a special operator: 1) function calls 2) macro forms 3) a small set of special operators, which are implemented as built-in functionality. One of them is a core conditional operator. More complex conditional operators then are implemented as macros, which expand eventually into the core conditional operator. The interpreter and compiler will have to specially recognize and implement these special operators. See IF: http://clhs.lisp.se/Body/s_if.htm http://clhs.lisp.se/Body/s_if.htm The usual Lisp view is that [(print 'hello)] is no useful improvement over (lambda () (print 'hello)) Some disagree, but most of the time the pattern (<operator> <expr0> ... <exprn>) is preferred in Lisp over <fancy-character> <expr0> ... <exprn> <another-fancy-character>
- skybrian 8y agoOkay, but if you have macros, you might as well use them. The idea is more about exploring what can be done when designing a less powerful language, without macros.
- lispm 8y ago> The idea is more about exploring what can be done when designing a less powerful language, without macros. Lot's of things. But the language won't be able to easily compute code at compile time, which a main purpose of macros. Lisp has a simple data format for source code and thus code transformations are relatively simple to do and even integrated into the language.
- reitzensteinm 8y agoWhat are lisp macros for if not to delay evaluation? I assume you're making a distinction between purpose and mechanism here? I've always understood the semantics to be: fn: (a b c) => (call a (eval b) (eval c)) mac: (a b c) => (eval (call a b c))
- lispm 8y agoThat might be more like what was called an FEXPR mechanism in some early Lisps or some niche Lisps, or even in languages like R. https://en.wikipedia.org/wiki/Fexpr https://en.wikipedia.org/wiki/Fexpr When an FEXPR is called, it gets unevaluated args and can then at runtime decide what to do. Macros OTOH are a different mechanism, where expressions get rewritten at macro expansion time, which can be for example at compile time. The macro then gets called with arbitrary expressions it encloses (those don't need to be valid code by themselves) and computes new source. Thus macros are code generators from expressions. In a compiled implementation, macro expansion is done with compilation interleaved: each form gets expanded, even repeatedly until it no longer expands into a macro, and then the resulting non-macro form gets compiled. Thus in a way a macro does not delay execution, it does the opposite: it actually shifts computation to compile time -> the computation of code from expressions and the computation of arbitrary side effects in the compile-time environment. In an interpreter version of Lisp, the macro gets also expanded at runtime - but in its own macroexpansion during evaluation. There eval will call the macroexpander repeatedly until it gets a non-macro form. Now, what can you do with arbitrary complex code generators at compile time? https://stackoverflow.com/a/2563308/69545 https://stackoverflow.com/a/2563308/69545 Actually Paul Graham wrote a classical Lisp book explaining a bunch of things around macros. Available here for download: http://www.paulgraham.com/onlisp.html http://www.paulgraham.com/onlisp.html The classic Lisp article which motivated the move from FEXPRs to macros: https://www.nhplace.com/kent/Papers/Special-Forms.html https://www.nhplace.com/kent/Papers/Special-Forms.html
- reitzensteinm 8y agoI don't think what I wrote is a FEXPR, which doesn't evaluate the result of the function call. I'm having a hard time parsing what you wrote or any of the sources you linked in such a way that says the semantics of macros differs from what I wrote (certainly the implementation gets a lot more complex and there are subtleties).
- mpweiher 8y ago> misconception that Lisp macros are mostly used to 'delay' evaluation. It was in the examples. No generalization to all of LISP was made or intended, though it would be interesting to make a survey study of actual macro usage by developers. It is also clear that you actually need the delay-evaluation mechanism in order to get something like "if" out of the language and into the library. > what Smalltalk calls 'blocks' are just (anonymous) functions in Lisp "but blocks/anonymous functions seem quite different from alternate argument-passing mechanisms." I think the history also explains why Smalltalk blocks were a bit odd as anonymous functions go: they did not start out as a "function" mechanism, they started out as just a delayed evaluation mechanism, and Smalltalk has no ordinary functions, just the anonymous kind. Which is also odd. The Smalltalk developers were well aware of LISP, cite it as a major source of inspiration in fact. They initially had a more macro-ish mechanism (the unevaluated token-stream), then moved to a pure delayed-evaluation mechanism which then turned into blocks. They never added a macro mechanism again. I think that's interesting. YMMV.
- lispm 8y ago> It is also clear that you actually need the delay-evaluation mechanism in order to get something like "if" out of the language and into the library. In a functional programming language one can just use functions. (defun l-true (a b) a) (defun l-false (a b) b) (defun l-if (f a b) (funcall f a b)) True is a function, false is a function and if is a function. Now we can do conditional execution: CL-USER 57 > (l-if #'l-true 1 2) 1 CL-USER 58 > (l-if #'l-false 1 2) 2 CL-USER 59 > (funcall (l-if #'l-true (lambda () (print 'true-case) nil) (lambda () (print 'false-case) nil))) TRUE-CASE NIL But Lisp doesn't do that. It provides one conditional as a primitive.
- mpweiher 8y ago>> need delay-evaluation [for "if" in lib] > one can just use functions Yes. Obviously. Of course. That's how Smalltalk does it, though it doesn't have general functions, it only has anonymous functions aka (roughly) blocks. Any other language that can pass functions or some sort of equivalent as arguments can obviously also do this. Heck, you can even do this, horribly, in C: #include <stdio.h> void a() { printf("true\n"); } void b() { printf("false\n"); } typedef void (*fnfn) (); void fnif(int cond, fnfn trueCase, fnfn falseCase ) { fnfn switcher[2]={ falseCase, trueCase }; switcher[cond](); } int main(int argc, char *argv[] ) { fnif( 0, a,b ); } (A little surprised that this compiled on the first try. More surprised that clang didn't show any warnings, even with -Wpedantic. Shows you how well I understand C/C compilers ¯\_(ツ)_/¯ ). And function definitions, lambdas, blocks etc. all delay the evaluation of their bodies.
- garganzol 8y agoDid you ever try to implement a macro in a Lisp? If you ever try, you'll quickly find out that a macro is essentially a lambda without argument evaluation. That's it. If we take another look on it, yep, that's a form of delayed evaluation of lambda parameters, however I would prefer the term "delayed expansion".
- phoe-krk 8y agoRelated discussion: https://news.ycombinator.com/item?id=14333824 https://news.ycombinator.com/item?id=14333824
- chriswarbo 8y ago> If you ever try, you'll quickly find out that a macro is essentially a lambda without argument evaluation. That's it. > If we take another look on it, yep, that's a form of delayed evaluation of lambda parameters, however I would prefer the term "delayed expansion". To me, 'delayed evaluation' implies something like call-by-need (e.g. Haskell) or call-by-name (e.g. Algol60), where we can't distinguish an evaluated argument from an unevaluated one, since any attempt to 'look at' (i.e. branch on the value of) the argument will force it to be evaluated. For example, if `(expensive-calculation)` evaluates to `42`, there could be no difference in the behaviour of `(my-delayed-function (expensive-calculation))` and `(my-delayed-function 42)` (although there would presumably be a difference in running time). In contrast, Lisp macros let us inspect the AST without forcing evaluation, so we can distinguish between these two values, and hence `(my-macro (expensive-calculation))` might behave completely differently to `(my-macro 42)`.
- aidenn0 8y agoConsidering the username of the person you are replying to, you might consider that they are very likely to have implemented macros in a lisp. In common lisp, defining a macro defines a function that receives as its arguments the unevaluated forms passed to it plus (optionally) the lexical environment. This part is indeed "essentially a lambda without argument evaluation." The magic isn't there, the magic is in the interpretation of the value it returns. It can return any arbitrary lisp forms. This allows macros to do far more than just delay evaluation. Pretty much anything that involves code walking is not possible with lambdas, for example. Some setf expanders would be impossible with lambdas as well.