Hacker Newsnew | past | comments | ask | show | jobs | submitlogin

Lately I read Graham's On Lisp and first felt it was one the greatest programming books I'd ever read and felt it was so close to perfect that the little things like he made me look "nconc" up in the CL manual (so far he'd introduced everything he talked about) made me want to go through and do just a little editing. And his explanation of how continuations work isn't very clear to me which is a problem because I can't find a better one online (the only way I think I'll understand continuations is if I write the explanation I want to read)

Then I start thinking things like: "if he was using Clojure he wouldn't be having the problems with nconc that he talks about" and "I can work most of the examples in Python because the magic is mostly in functions, not in the macros" and "I'm disappointed that he doesn't do anything that really transform the tree"

(It's still a great book that's worth reading but anything about Lisp has to be seen in the context the world has moved on... Almost every example in https://www.amazon.com/Paradigms-Artificial-Intelligence-Pro... can be easily coded up in Python because it was the garbage collection, hashtables on your fingertips, first class functions that changed the world, not the parens)

Lately I've been thinking about the gradient from the various tricks such as internal DSLs and simple forms of metaprogramming which are weak beer compared to what you can do if you know how compilers work.



> if he was using Clojure he wouldn't be having the problems with nconc that he talks about"

Yeah, one would write the implementation in Java.

Common Lisp (and Lisp in general) often aspires to be written in itself, efficiently. Thus it has all the operations, which a hosted language may get from the imperative/mutable/object-oriented language underneath. That's why CL implementations may have type declarations, type inference, various optimizations, stack allocation, TCO and other features - directly in the language implementation. See for example the SBCL manual. https://sbcl.org/manual/index.html

For example the SBCL implementation is largely written in itself, whereas Clojure runs on top of a virtual machine written in a few zillion lines of C/C++ and Java. Even the core compiler is written in 10KLOC of Java code. https://github.com/clojure/clojure/blob/master/src/jvm/cloju...

Where the SBCL compiler is largely written Common Lisp, incl. the machine code backends for various platforms. https://github.com/sbcl/sbcl/tree/master/src/compiler

The original Clojure developer made the conscious decision to inherit the JIT compiler from the JVM, write the Clojure compiler in Java and reuse the JVM in general -> this reuses a lot of technology maintained by others and makes integration into the Java ecosystem easier.

The language implementations differ: Lots of CL + C and Assembler compared to a much smaller amount of Clojure with lots of Java and C/C++.

CL has for a reason a lot of low-level, mutable and imperative features. It was designed for that, so that people code write efficient software largely in Lisp itself.


... I remember meeting Rich Hickey at conference when he'd seen a tweet where I'd favorably compared Clojure to Scala. I had a hard time explaining to him, however, how a professor who wrote FORTRAN programs to simulate the behavior of materials (other academics would be quite shocked when he'd explain that we actually could figure out that iron is magnetic from first principles... I regret missing out on his class on density functional theory as much as I regret not taking a compilers class) told me that a 2x difference in performance mattered so much when you were running big jobs in computers. Thus you weren't going to get everybody impressed with the power of immutability. I am writing a chess engine in Java right now and completely in tune with, in the long term, not making any objects at all in the inner loop or the next loop out.

But yeah, CL was one of the first languages specified by adults and set the standard for other specs like Java that read cleanly and don't have the strange circularity that you notice in K&R. So many people have this abstract view that a specification should be written without implementation in mind, but really the people behind CL weren't going to be implementable and clearly they'd given up on the "Lisp machine" idea and made something that the mainstream 32 bit machine could handle efficiently. It's quite beautiful and had a larger impact on the industry than most people admit.

(I think how C is transitional between failures like PL/I and modern languages like CL and Java that can be specced out in a way that works consistently)


Let me take a shot at explaining continuations.

In normal programming, functions "return" their values. In Continuation Passing Style (CPS), functions never return. Instead, they take another function as input; and instead of returning, they call that function (the "continuation"). Instead of returning their output, they pass their output as input to the continuation.

(Some optimizations are used such that this style of call, the "tail call", does not cause the stack to grow endlessly.)

Why would you write code in this style? Generally, you wouldn't. It's typically used as an internal transformation in some types of interpreters or compilers. But conceptualizing control flow in this way has certain advantages.

Then there are terms like the "continuation" of a program at a certain point in the code, which just means "whatever the program is going to do next, after it returns (or would return) from the code that it's about to execute". That's what "call with current continuation" (call/cc) is about. It captures (or reifies) "what will the program do next after this?" as a function that can be called to do, well, do that thing. If your code is about to call `f();`, then the 'continuation' at that point is whatever the code will do next after `f()` returns with its return value.

Thus if you had some code `g(f())`, then the continuation just as you call `f()` is to call `g()`. CPS restructures this so that `f()` takes the "thing to do next" as input, which is `g()` in this case. The CPS transformation of this code would be `f(g)`, where `g` is the continuation that `f` will invoke when it's done. Instead of returning a value, `f` invokes `g` passing that value as input.

You can use continuations to implement concepts like coroutines. With continuations, functions never need to "return". It's possible to create structures like two functions where the control flow directly jumps between between them, back and forth (almost like "goto", but much more structured than that). Neither one is "calling" the other, per se, because neither one is returning. The control flow jumps directly between them as appropriate, when one function invokes a continuation that resumes the other. The functions are peers, where both can essentially call into the other's code using continuations.

That's probably a little muddy as a first exposure to continuations, but I'm curious what you think. I generally think of continuations as a niche thing that will likely only be used by language or library implementors. Most languages don't support them.

Also, I'd probably argue that regular asynchronous code is a better way to structure similar program logic in modern programming languages. Or at least, it's likely just as good in most ways that matter, and may be easier to reason about than code that uses continuations.

For example, one use-case for coroutines is a reader paired with a writer. It can be elegant because the reader can wait until it has input, and then invoke the continuation for the writer to do something with it (in a direct, blocking fashion, with no "context switch"). But you can model this with asynchronous tasks pretty easily and clearly too. It might have a little more overhead, to handle context switching between the asynchronous tasks, but unlikely enough to matter.




Consider applying for YC's Winter 2026 batch! Applications are open till Nov 10

Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact

Search: