4 ms·
I 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 sourc
by reitzensteinm 8y ago
I 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).
- lispm 8y agoThat's why I wrote 'more like'. Given that you haven't defined any semantics of your operators, it's more like a guess. Not sure if this helps you. But let's define a macro A: CL-USER 32 > (defmacro a (b c) (print (list :macro-expansion b c)) (list 'print (list 'quote (list :runtime :b b :c c)))) A This macro does two things: it prints something at macro expansion time and then generates some code it returns as a value. Now we can use this macro in some code: CL-USER 33 > (defun test () (a 21 42)) TEST If we now compile the function, the macro gets executed and prints something: CL-USER 34 > (compile 'test) (:MACRO-EXPANSION 21 42) TEST NIL NIL We can also call the macroexpander independent of the compiler. The we a) get the side effect of the print statement and we see the result: CL-USER 35 > (macroexpand '(a 20 30)) (:MACRO-EXPANSION 20 30) (PRINT (QUOTE (:RUNTIME :B 20 :C 30))) T At runtime we call the test function: CL-USER 36 > (test) (:RUNTIME :B 21 :C 42) ; <- the printed output (:RUNTIME :B 21 :C 42) ; print also returns its arg Thus all the macro expansions have been done at compile time and we have generated some code there. No macro expansion at runtime. Thus it has to do with code generation and code execution at macro expansion - nothing about 'delaying' something. The example isn't useful, but imagine a macro INFIX (infix a * b + c) which rewrites the expression to the Lisp expression: (+ (* a b) c) There is nothing about 'delaying' -> it's just rewriting the form. Ideally at compile time. There are many other examples which do something different.
- reitzensteinm 8y agoBut an infix transform does delay the evaluation of expressions you've passed to it. Eg (infix (avg b) * (sgn a)). And most practical uses of macros involve passing in expressions that will be later evaluated verbatim, e.g. (time (reduce + (range 100))) I was only intending responding to this: "That's a common misconception that Lisp macros are mostly used to 'delay' evaluation." Which just seems to me confuses the matter in response to the article that was posted. At a very low level, macros operate by having the evaluation of their arguments delayed. At a higher level, they're used to implement sugared forms which usually contain valid code (though not necessarily code that makes sense in isolation). I know you're very experienced with lisp, and I'm not trying to correct you technically, but to me this stuff only clicked when implementing a metacircular evaluator and learning when and how to perform evaluation. The blog post to me reads like the author was going through a similar process.
- lispm 8y ago> But an infix transform does delay the evaluation of expressions you've passed to it. Eg (infix (avg b) * (sgn a)). It doesn't 'delay' anything. It just rewrites at compile time (infix (avg b) * (sgn a)) into (* (avg b) (sgn a)) That's all. At runtime only the rewritten statement will be executed. > At a very low level, macros operate by having the evaluation of their arguments delayed Not at all. The main purpose of macros is to compute code from arbitrary expressions at compile time. There is no 'delay' -> it's mostly about rewriting expressions at compile time. > implementing a metacircular evaluator and learning when and how to perform evaluation. You can build a macro expansion phase into an interpreter. That's what Lisp interpreters do. But the main purpose of macros is to enable code transformations at compile time and thus only to have a cost at compile time, not at runtime. In an interpreter the macro expansions would also happen at runtime and have a cost there. But Lisp compilers are popular and to make the most out of code transformations at compile time, the idea of macro and their expansion at compile-time was introduced. Thus one gets for example new embedded languages, without the need to parse them at runtime.
- reitzensteinm 8y agoI believe that if you're trying to understand what macros do from a fundamental level, treating them like functions with different semantics is a very good learning method. You implement them in a SICP style interpreter and then write a few. Totally clears up the mystery. I'm not arguing with you about what happens in any production lisp. I'm sure you know more than me, but I do understand how they're implemented in practice, and the warts that brings - eg no (apply or <list>), and first class macros are so esoteric purely from the runtime cost that they're barely even written about in the literature. I'm just saying the mental model of where macros fit in in an interpreter has value, and that view has not changed. In my opinion you're an expert coming in correcting a bunch of people attempting to learn the basics, and you're not wrong, but you are muddying the waters.
- kd0amg 8y agotreating them like functions with different semantics is a very good learning method If you mean run-time functions that act a bit funny about evaluation order, that mental model is incompatible with common introductory examples like `let`.
- User23 8y agoWhile experienced lispers already know this, it may not be clear to everyone that in Common Lisp "compile time" routinely happens repeatedly in a running image. cl-ppcre[1] relies heavily on this. It can take a regexp pattern as a runtime input and compile a recognizer down to machine code on the fly. This technique gets used routinely. Edit: In fact, I strongly suspect that this resulting unsuitability of Lisp for proprietary code played a major role in its commercial failure before the widespread advent of services. [1] https://edicl.github.io/cl-ppcre/ https://edicl.github.io/cl-ppcre/
- lispm 8y agoThat's not a Common Lisp specific thing. The first Lisp compiler in 1962 was already written and running inside Lisp. Whether the compiler runs inside an 'image' or in some batch mode is also not important for macros.
- hibbelig 8y agoA normal function call evaluates the arguments and then calls the function. Because macro expansion does something before evaluating the arguments, you can say that evaluating the arguments has been delayed. I feel this is just looking at the same thing from two different directions. Of course, macro expansion does _more_ than just delaying the evaluation of the arguments, and if people say that macros delay evaluating the arguments, you might think that's all they do.
- sparkie 8y agoA macro or FEXPR may never evaluate an operand at all. They simply receive the unevaluated forms and decide what to do with them, with explicit evaluation being just one option which can be performed on the operand. Delayed (lazy) evaluation merely prevents an operand being evaluated at the time of call and until you want to read its reduced value, after which it is automatically evaluated.
- lispm 8y ago> Because macro expansion does something before evaluating the arguments It does something independent of evaluation. When the code gets compiled, the macroexpander already has transformed the code. The code might never be evaluated in this Lisp. It might be written to disk and later be loaded into another Lisp. If something does not get evaluated, gets evaluated later, gets always evaluated or never -> that depends on the generated code. Thus 'delaying' something is the wrong idea and it limits the imagination of what macros are used for. Think of 'general code transformation', often independent of execution in a compilation phase.