Live data from Hacker News

Lisp Macros, Delayed Evaluation and the Evolution of Smalltalk

blog.metaobject.com

61–70 of 114 posts

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

#61
post #56
post #51

Earlier quoted context omitted.

> Delaying functionality is just one use of functions. This is why explicitly said Lambdas. Name one use of lambdas not covered by functions that is not delayed evaluation. If you want to be pedantic please be so consistently. > S-expressions know nothing about 'syntax'. Thus they can't be an AST. You seem to be confusing what the CL Hyperspec calls syntax, which is actually the semantics of evaluating a certain expr…

> This is why explicitly said Lambdas A lambda is a function. There is nothing special about them. There is nothing which makes them less or more functional than other functions. ((lambda (x) (* x 10)) 30) FYI: above is valid Scheme code. BiwaScheme Interpreter version 0.6.4 Copyright (C) 2007-2014 Yutaka HARA and the BiwaScheme team ((lambda (x) (* x 10)) 30) => 300 Above is lambda expression applied to an argument.…

FYI It's not valid scheme code, you're missing a closing paren.

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

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

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

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

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

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

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

> they did not start out as a "function" mechanism, they started out as just a delayed evaluation mechanism

Do you have a citation for this? Not disputing it, I'd just like to see whether there's something in Smalltalk's history I have overlooked, or didn't notice on my first reading.

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

#64
post #61
post #56

Earlier quoted context omitted.

> This is why explicitly said Lambdas A lambda is a function. There is nothing special about them. There is nothing which makes them less or more functional than other functions. ((lambda (x) (* x 10)) 30) FYI: above is valid Scheme code. BiwaScheme Interpreter version 0.6.4 Copyright (C) 2007-2014 Yutaka HARA and the BiwaScheme team ((lambda (x) (* x 10)) 30) => 300 Above is lambda expression applied to an argument.…

FYI It's not valid scheme code, you're missing a closing paren.

Thanks, updated.

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

#65
post #31

Earlier quoted context omitted.

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

"we should always use the least powerful mechanism that will accomplish our goal"

I like this when implementing something for non proficient users. But when it comes to providing tools for (supposedly) advanced users, like programmers... There's late-"socialism" joke in Bulgaria: "thrift is mother of misery". A designer doesn't know ahead of time what problems "creative" users will face long term. Providing a set of simplest mechanisms for today's challenges would possibly constrain them in the future - combination of multiple mechanisms in ways not foreseen may add large incidental complexity (like OO design patterns). Which could be avoided if less by count but more powerful mechanisms were used in first place. Macros have main role in keeping Common Lisp relevant to the latest paradigm hypes despite the standard being set in stone. Opposite to this, for example, C++ must keep introducing piles of new least-powerful mechanisms to keep pace.

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

#66
post #58
post #46

Earlier quoted context omitted.

Experience shows that most Clojure users come from Java and have very little idea about Lisp. It might help to bring in some perspective, since Clojure is not a very typical Lisp: no interpreter, no linked lists as base data structure, no Lisp in Lisp, no Lisp runtime, no images, not the typical debugging tools like break loops, the runtime is from the JVM or another environment (.net, JavaScript), ... Lisp existed a…

> no interpreter ClojureScript is interpreter, unless you run Closure compiler in compilation phase. Interpretation is done by default in repl. There is also Joker [0]. > no linked lists as base data structure Not true. Linked lists are base data structure on the same level as vectors and hash maps. Unlike Scheme where hash maps are sometimes implemented as assoc-ed lists. > no Lisp in Lisp Check for CinC and derivat…

> ClojureScript is interpreter

I thought ClojureScript compiles to JavaScript?

> What you define by "runtime"?

For example the stuff the JVM provides: memory management, data layout, interrupt handling, threads, loading code, talking to the environment, ...

> By this requirement, 90% Lisps out there would not be Lisp

I'd think it's more like 70% use images... just a guess. The exceptions usually are Lisps using runtimes which can dump/load images (like probably every Lisp on the JVM) or which by choice don't do (like some Lisp to C compilers, ...).

> Even some Common Lisp implementations (ABCL) doesn't have it.

That's the same limitation of the JVM. There are a few more implementations which don't have it. But generally there is a rich choice of full language implementations with and without images.

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

#67
post #42
post #40

Earlier quoted context omitted.

lambdas are anonymous functions. Macros are code transformers. Lisp code is not an AST.

Different name for the same thing, whats your point? Lambdas/AF are used to delay evaluation but keep the default semantics. Macros are more than simple code transformers, that wording somehow implies that they somehow retain the semantics of the data passed to them, which mighy be the case but is not required at all. S-Expressions are just a serialisation format for the m-expression AST.

I thought I'd weigh in, as a casual Racket/Elisp user

> Lambdas/AF are used to delay evaluation but keep the default semantics.

You and lispm seem to agree that lambdas can be used to delay evaluation, e.g. `(lambda () (+ 1 2))`. They are also used to abstract/parameterise, e.g. `(lambda (x) (* x x))`.

These two ideas coincide in most languages, since it's often not clear how we would evaluate a parameterised expression (e.g. how might we evaluate the `(* x x)` above, whilst keeping `x` as a free parameter?). Hence most languages don't evaluate inside a parameterised expression (lambdas) until called, and hence we can use them to delay evaluation.

In fact, we can evaluate "under a lambda", which is what happens during inlining, constant folding, or more generally partial-evaluation and supercompilation. This shows that parameterising and delaying evaluation are two distinct ideas, but they're usually represented with one language construct (lambda). We could imagine a separate language construct, like `(delay foo)`, which prevents any form ofevaluation (including inlining, supercompilation, etc.) from being applied to its argument, until it's 'forced' (maybe with a 'force' construct, or maybe by overloading function call syntax).

As for whether Lisp code is a syntax tree I would say:

- If we're allowing the possibility of the reader being altered, then our code doesn't have to be a tree, but I personally wouldn't call it "Lisp code" if it differed that much from s-expressions, regardless of whether a Lisp implementation can read it or not. In that case it's more like we're repurposing a Lisp implementation to act on some non-Lisp language. For example, if we used reader macros to consume regular expressions, that doesn't mean that regular expressions are Lisp.

- To me "Lisp code" is a syntax tree, but it is concrete not abstract (for the reasons lispm says, e.g. `+` is just an opaque symbol rather than an arithmetic operator; we can interpret it in any way we like, using macros).

- I wouldn't say that Lisp code is s-expressions, since there are alternative representations (e.g. I-expressions, sweet-expressions, etc.). Yet as long as they're equivalent to s-expressions (which, in particular, requires that they're trees and that we can impose our own semantics using macros!), then I'm happy calling them Lisp.

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

#68
post #42

Earlier quoted context omitted.

Different name for the same thing, whats your point? Lambdas/AF are used to delay evaluation but keep the default semantics. Macros are more than simple code transformers, that wording somehow implies that they somehow retain the semantics of the data passed to them, which mighy be the case but is not required at all. S-Expressions are just a serialisation format for the m-expression AST.

I thought I'd weigh in, as a casual Racket/Elisp user > Lambdas/AF are used to delay evaluation but keep the default semantics. You and lispm seem to agree that lambdas can be used to delay evaluation, e.g. `(lambda () (+ 1 2))`. They are also used to abstract/parameterise, e.g. `(lambda (x) (* x x))`. These two ideas coincide in most languages, since it's often not clear how we would evaluate a parameterised express…

> You and lispm seem to agree that lambdas can be used to delay evaluation, e.g. `(lambda () (+ 1 2))`

Right, and this is not different from any other function:

  (define (foo) (+ 1 2))

  (define bar (list foo (lambda () (+ 1 2))))  ; putting two functions in a list for later evaluation

  ((first bar))
  ((second bar))
> They are also used to abstract/parameterise, e.g. `(lambda (x) (* x x))`.

yep.

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

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

`if` has two distinguishing features:

- It selects between two possibilities

- The non-selected possibility isn't evaluated/executed (i.e. the evaluation of the possibilities is delayed until after one has been selected)

This is very important for imperative languages, since executing both branches would have observable effects even if we only returned one result.

This is also true in functional programming languages, even pure ones, since it's needed to e.g. terminate recursive calls. For example, if we define factorial by branching on whether the argument is 0 (base case) or not (recursive case), then evaluating both branches before selecting one would cause an infinite recursion for all arguments.

In the case of your Lisp code, we couldn't implement a recursive function like factorial unless we delay the evaluation e.g. by using macros to do the selection, or by switching from Lisp's default evaluation order (call-by-value) to something non-eager like call-by-name or call-by-need.

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

#70
post #42
post #40

Earlier quoted context omitted.

lambdas are anonymous functions. Macros are code transformers. Lisp code is not an AST.

Different name for the same thing, whats your point? Lambdas/AF are used to delay evaluation but keep the default semantics. Macros are more than simple code transformers, that wording somehow implies that they somehow retain the semantics of the data passed to them, which mighy be the case but is not required at all. S-Expressions are just a serialisation format for the m-expression AST.

Note that you may also have a Lisp form like #1=(programmable . #1#), which represents not a tree but a graph. With lispm's remarks in mind, you can see that it's not abstract, not syntax, and not a tree.
Post reply on HN