Live data from Hacker News

Lisp Macros, Delayed Evaluation and the Evolution of Smalltalk

blog.metaobject.com

31–40 of 114 posts

Re: Lisp Macros, Delayed Evaluation and the Evolution of Smalltalk

#31
post #24

Macros work much better in homoiconic languages like Lisp than they do in other languages. Most of the code I write these days is in Elixir and I avoid macros unless absolutely necessary. The benefits generally don't outweigh the (long-term) costs.

One of the differences between macros in Lisp and some other languages which provide macros is that the expressions themselves don't need to be valid code in some programming language -> they don't get parsed by a language parser upfront.

Thus I can write a postfix macro and then write code like this:

  (postfix 2 3 + 3 *)
even though Lisp requires code to be nested prefix expressions.

Some other languages won't allow this, because the expression 2 3 + 3 * is not legal in their language. Thus it only may allow macro transformations from legal expressions to other legal expressions...

Re: Lisp Macros, Delayed Evaluation and the Evolution of Smalltalk

#32
post #16

Earlier quoted context omitted.

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).

That'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 val…

A 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.

Re: Lisp Macros, Delayed Evaluation and the Evolution of Smalltalk

#33
post #28

Earlier quoted context omitted.

> 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 'block…

> 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 #'…

>> 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 

   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.

Re: Lisp Macros, Delayed Evaluation and the Evolution of Smalltalk

#34
post #16

Earlier quoted context omitted.

That'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 val…

A 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 evaluati…

A 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.

Re: Lisp Macros, Delayed Evaluation and the Evolution of Smalltalk

#35
post #31
post #24

Macros work much better in homoiconic languages like Lisp than they do in other languages. Most of the code I write these days is in Elixir and I avoid macros unless absolutely necessary. The benefits generally don't outweigh the (long-term) costs.

One of the differences between macros in Lisp and some other languages which provide macros is that the expressions themselves don't need to be valid code in some programming language -> they don't get parsed by a language parser upfront. Thus I can write a postfix macro and then write code like this: (postfix 2 3 + 3 *) even though Lisp requires code to be nested prefix expressions. Some other languages won't allow…

> don't need to be valid code

Yes. One of the examples from the talk was a comment macro. Very cool (and the talk was about fun/cool stuff, not about practicalities).

The question is whether you want that sort of power in day-to-day programming. My guess is no. That's also what the PARC/LRG folks found out with Smalltalk-72. It's also something I hear from some very seasoned LISP hackers. It's also the sense I am getting from these very powerful DSL/LOP workbenches.

From TFA:

The reason the question is relevant is, of course, that although it is fun to play around with powerful mechanisms, we should always use the least powerful mechanism that will accomplish our goal, as it will be easier to program with, easier to understand, easier to analyse and build tools for, and easier to maintain.

It's also why I use the C pre-processor very reluctantly. Though I do use it. From time to time. And then try to get rid of that use if I can[1]

And no need to explain how much better LISP macros are :-) In a sense, like Smalltalk, LISP may be just too powerful, in the words of Alan Kay "Lisps frequently 'eat their children'" so that there's always an answer (use a macro) that will cut off an interesting question.

[1] https://blog.metaobject.com/2018/11/refactoring-towards-lang...

Re: Lisp Macros, Delayed Evaluation and the Evolution of Smalltalk

#36
post #2

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/...

What 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))

I think it's "'delayed evaluation' + 'transparent internal structure'". So delayed evaluation isn't enough, because the only thing you can do with N closures is to call them in any order, you can't create a complex new expression dependent on the substructure of the closures.

The closest thing to a macro in (eg. python) imo looks like this.

Instead of

  d = c * (a + b)
  class Adder: 
    def __init__(self, left, right): 
      self.left=left 
      self.right = right

  class Multiplier: 
    def __init__(self, left, right):         
      self.left = left 
      self.right = right

  d = Multiplier(Adder(a, b), c)
Fill out the __call__ function and/or create an evaluator that recursively calls each class instance with it's parameters and you've made a system that works a lot like a lisp with a macros. Now you can do broadly /anything/, eg. replace every instance of an adder in the call tree with an adder+1. That's using the second required element of my description of macros- the complex substructure, to perform arbitrary transforms. You can't do that with just closures, unless they make their bound parameters accessible (and normally they don't directly, in python they do because it's absurdly dynamic). Of course this is not at compile-time, it's at "delayed execution time", but the difference is kind of slight imo. The benefit of a real lisp over doing this in python is that you don't need to rewrite every fn, operator, control flow statement into your own abstracted level by hand, and it's much faster. Also you can complete the loop and trivially write it back down into eval-able source code text.

Re: Lisp Macros, Delayed Evaluation and the Evolution of Smalltalk

#37
post #26

Algol 60 is wildly underappreciated, and pass by name[1] is a great example. [1] http://www.cs.sfu.ca/~cameron/Teaching/383/PassByName.html Edit: An elementary error is to assume that call by name is equivalent to pass by reference. It's not.

"Here is a language so far ahead of its time, that it was not only an improvement on its predecessors, but also on nearly all its successors" - Hoare

http://www.computernostalgia.net/articles/algol60.htm

Re: Lisp Macros, Delayed Evaluation and the Evolution of Smalltalk

#38
Lambdas are about delayed evaluation. Macros are about disabling evaluation. Lisp code is just an abstract syntax tree notation format (s-exps) that comes with default evaluation semantics. Macros allow you to disable those evaluation semantics to reuse the AST for a different programming language.

So for example to add pattern matching facilities to the language, you come up with a syntax and its representation in s-exp AST and then write a macro that describes the unification operation using the default semantics.

Same for logic programming or any other paradigm not initially supportet.

Languages like clojure also bootstrap quite a bit of the language from a simpler to implement dialect. (look at all the functions with a * in clj source they're the base language)

Re: Lisp Macros, Delayed Evaluation and the Evolution of Smalltalk

#39
post #16

Earlier quoted context omitted.

That'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 val…

A 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 evaluati…

> 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.

Re: Lisp Macros, Delayed Evaluation and the Evolution of Smalltalk

#40
post #38

Lambdas are about delayed evaluation. Macros are about disabling evaluation. Lisp code is just an abstract syntax tree notation format (s-exps) that comes with default evaluation semantics. Macros allow you to disable those evaluation semantics to reuse the AST for a different programming language. So for example to add pattern matching facilities to the language, you come up with a syntax and its representation in s…

lambdas are anonymous functions. Macros are code transformers. Lisp code is not an AST.
Post reply on HN