Hacker News new | past | comments | ask | show | jobs | submit login
Common Lisp's block / return-from and unwind-protect (axisofeval.blogspot.com)
126 points by bshanks 11 months ago | hide | past | favorite | 61 comments



Block/return-from is a lexical transfer of control that can be non-local (you can transfer out of a lambda or defun to an enclosing scope). For the dynamic equivalent there's catch/throw. Both transfers activate unwind-protect if they unwind the stack through it.


One of the only languages where I saw this non-local returns is Kotlin.

You can return from a function from within a lambda (and I believe this works on any number of levels), for example:

    fun go(list: List<String>): Boolean {
        list.forEach { if (it.isEmpty()) return@go true }
        return false
    }


Longjmp in standard C, swapcontext in POSIX C. I think GCC allows gotos to the containing function from a local function (but I never used GCC local functions).

Smalltalk, and I believe ruby, allow non local return from blocks.


longjmp simply restores the machine registers (including PC and SP) and thus doesn’t respect any sort of unwind-protect, a concept unknown in C.

unwind-protect is a more general form of the c++ raii (in fact c++ got raii from Common Lisp).


`longjmp` does not restore the machine registers on all platforms. The C standard guarantees only the contents of variables marked `volatile`: anything above that is implementation-dependent.

I think that MSVC++ longjmp actually does proper unwinding, calling destructors in C++ functions on the stack, but don't quote me on that. I think it is also dependent on the compiler flags.


Implementing exception handling (with cleanup on unwind) on top of setjmp was not uncommon. GCC still does on some targets. But I'm sure you know this more than I do.

I think that RAII is different from unwind-protect and other scope based cleanup (finally, defer, with) as it is tied to object lifetimes. The fact that automatic objects lifetimes are tied to scope is a nice feature, but RAII goes beyond that.


Well lisps don’t really have destructors in the sense C++ does; objects go out of scope and become unreachable. So unwind-protect makes cleanup (closing a file, for example) explicit, which it has to be.

I wouldn’t say RAII is “tied to object lifetime” except in its name; at least I think of it as every {} pair defining an unwind-protect with object creation/destruction being how it is effected by the programmer. Perhaps that is the same thing as you said, simply viewed from opposite sides.

I do like the automatic nature of RAII, though implementing a whole class for it always feels clumsy to me even after doing it for decades.


What I mean is that if I add an instance of a class with a non trivial destructor from a container and later remove it, the destructor is automatically invoked. This is different from an unwind protect.


The object is not exactly removed from the container. It is removed from existence. The object in that situation resides in the container physically. It is constructed there and destroyed. It does not exist outside of the container afterwards, or at any other time.

This is a low-level memory management strategy being conflated with object-orientation, that is alien to higher level languages.


Well yes, it works well in c++ because the language has a strong distinction between value semantics and reference semantics, so when you remove a value from a container it must clearly be destroyed. A GC'd language with aliased references can't call destructors in this case. At best can do it when the object is collected.

Yet I think an high level language could have the same value/reference distinction. Or you could do it with linear types.


Lisp uses UNWIND-PROTECT to implement that.

For example WITH-OPEN-FILE closes the file automatically when leaving its scope.

     (with-open-file (stream "hello.world")
       (read stream))
WITH-OPEN-FILE is a macro and expands into OPEN and CLOSE operations, protected by an UNWIND-PROTECT.

Thus anything where a destructor would automatically clean things up is done in Lisp behind the scenes automatically using an UNWIND-PROTECT form.


That's again an example of cleanup associated with scope. I understand what you can do it with unwind-protect and friends.

But the essence of RAII is more than that.

In c++ if I add an opened file object to some collection and later remove it or destroy the collection, it is implicitly cleaned up.

For example let's say you are implementing an n-way out of core merge: in c++ you would create a priority queue of files objects ordered by the front current from item. You pop the front file object read the item and, if the file is not empty, push it back into the queue. Cleanup is implicit by removing the file from the queue and not explicitly adding it back. On early exit (because of an error or exception) the queue is automatically destroyed and recursively all the file objects.

There might be a way to implement this recursively with unwind-protect, but I think it is less natural.

For example python has ExitStack, which in practice is an ad-hoc container that supports recursive cleanup of contained objects, but is still not as convenient as having all containers do proper cleanup.


>> in fact c++ got raii from Common Lisp

I know and understand this is true.

Can you point to any sources (projects, papers) that further substantiate this?


The concept of RAII has emerged gradually and it would be hard to pinpoint a moment for its appearance. I am not aware of any specific feature of RAII that has appeared in Common Lisp before other languages.

The concepts of constructors and destructors have been introduced by C.A.R. Hoare in November 1965, in "Record Handling", a proposal for the extension of Algol.

At that time, Hoare was using the Cobol terms, i.e. "record" for what later was named "object" and "record class" for what later, in Simula 67, was abbreviated to "class". All the "records" discussed by Hoare were allocated dynamically, in the heap.

Constructor (Hoare, 1965-11): "In order to bring records into existence in the first place, the record class identifier should be used as if it were a function designator"

Destructor (Hoare, 1965-11): "a standard procedure "destroy" is proposed, which takes as parameter a reference to a record, and which reverses the effect of having created that record"

The next step towards RAII has been done by Bjarne Stroustrup in "C with Classes" in April 1980, when he made the invocation of the destructors implicit at the exit from a block, by introducing the special member function "delete" for this purpose. Despite the name, the 1980 "delete" member functions corresponded to what later, in C++, were renamed as destructors.

So in 1980, RAII was complete, but it was not yet promoted as a universal strategy for managing resources.

In 1980, Common Lisp did not exist.

Most older Lisps did not have any concept similar with the Algol block, so it would have been impossible for them to invoke implicitly some cleanup functions at block exits. They relied only on the garbage collector, where there is no RAII in the Stroustrup sense, even if GC and RAII are alternative methods for avoiding the explicit invocations of "free", "close" and the like.


MDL had UNWIND in 1977 and Maclisp added UNWIND-PROTECT in 1978. I'll guess that Lisp Machine Lisp then got it, too. Common Lisp emerged 1981 onwards, largely based on the latter.

In the Lisp Machine OS there is a concept called RESOURCE for manual memory management. For example the CHAOS network stack has to deal with network packets. There is a macro USING-RESOURCE, which allocates/gets an object and on exit frees it. The macro expands into a form using also UNWIND-PROTECT to ensure freeing a resource on non-local exit. I would think that this is from around 1980, for the MIT CADR machine.


Thanks for pointing to MDL:

http://www.ifarchive.org/if-archive/infocom/info/MDL_Primer_...

Nevertheless, the MDL UNWIND and the later UNWIND-PROTECT are more limited in applications and they require much more work from the programmer than the mechanism introduced by Stroustrup in 1980.

With implicitly-invoked destructors, the destructor body is written once for each type of data, and normally there is no need to ever invoke it explicitly.

After writing correctly the constructors and destructors, the programmer's work becomes identical with using a garbage collector, because the objects are allocated explicitly, but they are never deallocated explicitly.

On the other hand, UNWIND is intended for handling exceptions. It can also be used as a normal cleanup strategy, but it still must be written every time for handling the exit from a block or from a hierarchy of nested blocks. In the latter variant, there is some economy in code writing, but the lazy deallocation is less efficient.

The UNWIND of MDL has little resemblance to RAII, but it resembles the UNWIND of Mesa (programming language used at Xerox, starting with 1976, which has introduced many innovations that have been included only much later in most programming languages).

It would be difficult to determine whether UNWIND has appeared first in MDL or in Mesa, or if both have taken it from another language, because experiments with exception handling were fashionable during those years and there were many places where various variants were tried.


UNWIND-PROTECT is a building block.

Resource management in the MIT Lisp OS ca. 1980, approximate example.

One would define a resource of arrays, where arrays can be allocated and deallocated. They will be managed via a pool.

    (defresource 2d-array (rows columns)
      :constructor (make-array (list rows columns)))
Now user code would use the USING-RESOURCE macro, where it spans a dynamic scope. Entering the scope allocates the resource. Inside the scope the resource is allocated. Leaving the scope will automatically deallocate the resource and put it back into the pool. A deinitializer may free memory as needed.

    (using-resource (my-array 2d-array 100 100) ; get me a 100x100 array from a pool
      (setf (aref my-array 42 42) 'the-answer)  ; setting the array
      (print (aref my-array 42 42)))            ; reading the array
To make sure that the resource gets deallocated, the above macro form will expand to something using UNWIND-PROTECT:

    ...
     (unwind-protect                                            

       (progn                                                   ; protected form
         (setf my-array (allocate-resource '2d-array 100 100))  ;   allocate the array
         (setf (aref my-array 42 42) 'the-answer)               ;   setting the array
         (print (aref my-array 42 42)))                         ;  reading the array

       (when my-array                                           ; exit form
         (deallocate-resource '2d-array my-array)))             ;   deallocate the array
    ...
This is an example of manual memory management using a pool of resources, where the DEALLOCATE is done always via UNWIND-PROTECT.

Thus the user will not explicitly use UNWIND-PROTECT, but some macros which use it in their expansion...


Thanks for the explanation.

I agree that this is pretty much equivalent to RAII.

Nevertheless, it is also obvious that this was not a source of inspiration for Bjarne Stroustrup.

He has started directly from the constructors and destructors of C.A.R. Hoare (1965-11) and Simula 67 (1968-05, Kristen Nygaard & Ole-Johan Dahl).

The only change is that in 1980 he has enhanced his compiler for "C with Classes" to generate automatically all the invocations to the appropriate destructors in all the block and function epilogues, relieving the programmer from this task.


No, but Tiemann might remember as I remember introducing him to unwind-protect (not that he invented RAII afaik, nor would claim to have)


Smalltalk only allows the return from the block's lexically enclosing method, not an arbitrarily specifiable context like Common Lisp does. It's discussed a bit on c2.com [1]

[1] https://wiki.c2.com/?SmalltalkBlockReturn


In Pharo or Squeak, you can just send the "return:" message to any context (except contexts that have nowhere to return).


swapcontext was in POSIX.1-2001; it was removed in 2008.

In relation to all this, in POSIX, there are often good reasons to use sigsetjmp and siglongjmp rather than setjmp and longjmp, because these also save and restore the signal mask. If you jump out of a context that locally disabled certain signals, you likely want them restored, like you would if that code returned normally. (It doesn't necessarily have to be a jump out of a signal handler!)


Gah, another Lisp post to tempt me to add yet another mini project to my plate...I always am curious about trying more Lisp because I keep seeing commentary about how powerful it is to actually build applications once you get moving on building things.

Anyone here have any recent practical experience in this direction who would confirm this in Lisp vs in other programming languages? Does effort in Lisp really compound/produce great code and great applications that much faster than other languages? (For web development primarily)


I've written a few small CL web applications( with about 100 users active a day), along with a few terminal applications (with only ~10 users). Other languages I know is C , Erlang, Elixir and a bit of python. This will end up sounding a bit evangelical but so be it.

I feel that the lisp code was easier to write and reason about, the being able to hot reload code from the repl significantly decreased the time to completion. I also 'connect' to a networked repl when sentry reports an unhandled error to figure out what has gone wrong.

You might be tempted to believe that CL repl is "similar enough" to python's repl, however this is NOT the case. Being able to redefine functions, variables and macros while working on the code (without a restart) allows you to deal with errors.

The syntax is a 'no brainer', Extreme consistency in function calls means that you don't need to think about it. Other languages which SOMETIMES use infix, sometimes require brackets, that is just crazy.

Lisp libraries have less churn than 'modern languages', some libraries have not been touched for some time, unlike python/ruby/js. The code does not seem to rot and old lisp code runs on modern implementations.

I work in emacs, lem, I know people who use vscode and alive, and vim. There is no 'hard' requirement to use emacs, you will get by as long as you have 'emacs like' repl integration.

All in all, I do not regret working in lisp. It doesn't have the cult of the other languages and I'm fine with that.


I'd rather say there is a definite cult over lisp. But it's very different from the other languages which I'd qualify as rather hardcore. Lisp's cult is a magical cult. No other language as such amazing stories as the space probe remotely debugged from earth with a repl, the extreme hot reload and mystical intertwining of assembly on PS2 of Jak & Daxter's GOAL, the MIT course with a Fantasia level of teaching, The symbolic machines, etc.

The only other language like that I can think of is smalltalk.


When do you work in emacs vs lem?


So, I've looked at lem. Lem seems to be a really great start and push in the right direction.

I work in LEM for my personal projects, that dont have other LSP integration needs and hooks into my org-mode/org-babel workflow. These should not be show stoppers, but will eventually be a non issue.

There are however some features that I'm missing in LEM before I can fully switch over.

1. The documentation for Lem is lacking. 2. I have lazy hands and hit shift on some keys when I shouldn't (shift and backspace for example). I'd need some way to ignore that. 3. An built in undo, (c-x u equivalent). 4. A documented library interface (this is kinda #1 again)

I will eventually get around to making my own plugins to solve most of these. Its a matter of time vs tradeoff.


I think lem looks really promising, buuuut .., I like org and tramp and slime-connect and structural editing and all the other great emacs things. Maybe I’ll try it out in the next few months or so . . .


Its slime is only local for now. Nrepl is something that I might work on this year.


For anyone following along there is a method to get this to work.

Download micros, put it in your local-projects

git clone https://github.com/lem-project/micros

Load it.and run.

(micros:create-server :port 50000 :dont-close t)

Then use m-x slime-connect in lem.

It is not slime, but it works.


I use a couple web apps in production©. The development experience is awesome, deployment is easy, the runtime is fast, the language and the ecosystem are stable. CL being CL, you get many errors, including type errors, at compile time (hitting C-c C-c, the feedback is instantaneous). You have less needs for stupid unit tests. My Sentry dashboard is empty :) Coming from Python I appreciate all that.

We have a choice of libraries, but no big batteries-included web framework, so you must be ready to assemble pieces (or simply, to know web development). Ex: a login system? There are examples to look at, not a use-and-forget library.

A CL webserver coupled with a client library like HTMX is great. I don't recommend Reblocks (weblocks) or CLOG yet for serious stuff.

resources: the Cookbook, my lisp-journey #web content, my last youtube video (@vindarel channel).


Thank you very much for referencing your latest video, I'm watching it and glad to have such a timely example of what the development process can actually look like for CL web applications. Really cool stuff, will subscribe. :)


Lisp is great for creating your own domain specific languages. S-expressions are basically abstract syntax trees written as code. Thus you can take any code written in any language and "lispify" it into an S-expression. Then you can apply various transformations/analyses on this expression to achieve various goals:linting, semantic modeling, compiling to machine code/bytecode, transpiling to another laguage, interpreting, optimizing, etc. Common Lisp also has the possibility of on-the-fly recompilation of classes and methods/functions which can be useful for debug and incremental development.I don't have much experience in the web development area but I would incline to think that you won't get much added value from using CL compared to other languages/frameworks. It can be useful if you want to develop your own framework with custom language constructs and features. But if you want to fast prototype a webapp idea you're better off trying the more popular solutions.


Not so recent, about 6 years ago, I had to communicate with many instruments (PS, Oscilloscope, SMU, etc) to test a new ASIC we developed. There where hundreds of tests to be done. We wanted to automate the measurement and evaluation of results, because they were so many. Also there where 5 fabs and 2 suppliers with different design, but “pin compatible”. I did the whole framework in emacs lisp, I had to implement ONC RPC and LXI on top to speak to the instruments… The whole thing took me 2 weeks. At the time I had no idea of Elisp; only a little vit of scheme. I was proficient in C, and I would say it had taken me at least 6 Months to do the same


I'm learning programming at age fifty-four and have chosen Common Lisp as my one language for life. If you want something easy to learn with a ton of support pick Python, Elixir, and/or JS. CL sucks for mini-projects since the learning curve requires serious commitment and is SO different from what people are used to. CL requires a long-term commitment and demands the programmer challenge pop programming trends and deeply entrenched styles. You have to think beyond today to what could be and what once was. But if you're willing to do that, there is no better language. Not Haskell, not OCamel, not Rust/Go/Julia/Clojure/Scheme, or the current lang du jour. Whatever limitations Common Lisp had in the past are long gone.

SBCL is blazingly fast, CL abstractions are profound yet practical. The type system gives you power without pettiness. Language-oriented programming is a superpower in a world of cut and paste library-oriented programming. Image-based computing often allows one to skip the impedance mismatch with DBs. In many cases and you can just persist the data structures as they are or serialize them where needed. The tooling with Roswell, Quicklisp, Qlot, Doom Emacs, and Sly make prototyping and exploratory programming wiz by. CLOG, Reblocks and Hunchentoot provide powerful abstractions for web programming, but Phoenix with Liveview is probably better for most. The debugging experience is so insanely powerful it freaks me out and makes other systems look rudimentary. CLOS, the object system, is best of breed and makes most of the standard criticisms of OO programming moot. The homoiconic list-based syntax and prefix notation are beautiful and never get in your way. Parentheses are invisible. The condition system makes flow control and error handling in other systems seem rigid and convoluted. Aspect-oriented programming is just what we do, not a thing tacked on. Macros and the meta-object protocol allow you to build a language on top of Lisp that allows users to conceptualize problems using nouns and verbs they already know.

Drop into Coalton or Shen for strong, static typing where necessary, or April (hosted APL) for array wizardry that makes NumPy look like data-flow programming in Basic on a Timex Sinclair with a cassette drive. You can slice and dice whole datasets in a few characters of code. Bundle existing C libs with CFFI when you find a good one you need. Lisp-based systems are alive and grow and change as requirements do. It's harder to program yourself into a corner. Refactoring is continuous and mundane. In-source documentation is the right place for it, but Lispers tend to be shamefully lax in providing it. Shaaame! Newer Lispers tend to be better about docs--the culture is changing. Often you ask a question and get no response. Other times you get a detailed response from a legendary mind and just sit there in awe working your way through their thinking and feeling your inadequacy. The code is about as succinct as it can be to maintain readability. There are two camps on readability, some experienced Lispers find it harder, and most find it much easier to parse than ALGOL-derived syntax.

Once mastered, quick things are quick, hard things are straightforward, and impossible things are doable. This is a system for people who need, not want, to change the world. It gives single programmers and small teams the power to take on armies of bureaucratic programmers. But you have to be willing to break the mold, handle charges of elitism, not be required to justify your choice to managers, tolerate a small hiring pool, train newbies hard and long, not get freaked out using some libs that are stable and haven't been updated in years, deal with the pace of development of a small community, have the patience and joy in problem-solving to handle frequent bafflement, read a ton of source code rather than search the forums, and be willing to roll your own when needed. If you can handle all the idiosyncrasies and have a burning need for elegance and power, then CL will reward and surprise into until dementia takes you.

In short, all the good things you've heard are true, many of the standard critiques are uninformed, and problems people talk about that apply to most languages often don't apply.


+1 nice summary! I have been using Common Lisp since the early 1980s and I also love the language. For my own Lisp hacking pleasure, I add a few Schemes to the mix, but that is often a (fun) distraction for me, and I would be better off sticking with CL (or Python for deep learning projects).

Do you have a blog where you share your learning experiences?


OMG! This is what I'm talking about with Common Lisp. One minute you're a washed-up slacker, the next Mark L. Watson speaks to you. I'm winding my way through "Loving Common Lisp, or the Savvy Programmer's Secret Weapon" as we speak and loving it. Thank you!

I don't have a blog and haven't gotten far enough in my learning to make anything useful, but I do plan to produce a newbie's guide to help people get started quickly. My perspective as an older person for whom CL is my first language could be valuable since my sticking points are different and I don't yet have bad habits to break.


Thanks!


Hello my friend, I am very pleased to make your acquaintance and I am very excited about your journey. I am also very impressed that for someone who is newly learning the field, you already are so well-spoken and knowledgeable about the language and the tools. Thank you so much for your response. I also appreciate that you are only a little older than my parents, and if you are learning so well and so quickly then that fills me with much inspiration and excitement for the prospects of continued enjoyment of programming and software development for more decades to come. It's really exciting to know that anyone can learn at any age, and I wish you much enjoyment and success on your journey. If you were to create a developer blog, I'd read it!

So far the language I've experienced the most power with is Python, and your description of Lisp's power and tooling are very intriguing. I will take note of your recommendations and play around with the tools you described. It is worth the investment if it means unlocking something even more powerful than Pythonic thinking and code.

Do you find that one needs to be well-versed in Emacs to fully appreciate the CL stack you described and the capabilities of the language itself? Would you learn Emacs separately/after spending time building things with CL, or is building things with CL in Emacs part of the entire experience?


Thank you! Do come in, the water is fine. If you keep Python for it's applicability today and pick up Common Lisp for it's practicality tomorrow, I don't see how you could go wrong.

Emacs is the weakest link in the Common Lisp experience but, yes, you have to defeat that boss to move up. I love/hate Emacs and mostly struggle to do anything. GNU Emacs is a ball of mud with decades of technical debt in dire need of a ground-up re-imagining. Emacs Lisp made a lot of inexplicable choices but it is a Lisp and Lisp is why Emacs survives. RMS' stewardship has been consistent but underwhelming. He's no Jose Valim. The editing model is inadequate, as shown by Kakoune and structural editors. And yet, Emacs is alive and malleable while so many other editors/IDEs are inorganic artifacts that start to rust as soon as they are released. We do have the nascent Alive for VSCode and VIM options, but they are not really improvements over Emacs, nor are they likely to be. VSCode is the latest CodeWarrior and will suffer the same fate. It's only propped up by daddy's money.

Doom Emacs tooling, package management, and sensible defaults take much of the pain out of Emacs. Sly, the LSP-like Emacs mode for CL, is too much fun and gives a hint of what Lisp Machine development must have been like. Remote editing and daemon mode are so practical. Once you've painstakingly hammered out a config and workflow, Emacs starts to hum along pleasantly. But getting there is relentlessly frustrating and trying something new sends you right back to the treadmill. Once I figure out why Lem SDL2 (an Emacs clone in Common Lisp) isn't building for me, I'll attempt a switch.

I have daily fantasies of jumping back to my high school graduation in 1986 with everything I know now. I'd put up sexy posters of Guy Steele and Alan Kay in my dorm room. The only real advantage of starting programming at my age is that I know what I want and why. I'm following the humanistic thread of computing from Paul Otlet -> Vannevar Bush -> Doug Englebart -> Ted Nelson -> Bret Victor. But I read the same chapters over and over and still haven't written any code beyond homework exercises. The task feels Sisyphean and my trauma-addled, post-alcoholic brain struggles to stay focused. My only consolation is that I can't think of anything better to do with my remaining time and the Common Lisp community is good company. I love being around eccentric people who are much smarter than me.

Python is an eminently practical, perfectly respectable choice. So naturally, my life goal is to kill it. In my head, Symbolics was open sourced after the AI winter and grew to become the obvious choice for most tasks. I wish that people wrote new languages as DSLs on top of Common Lisp rather than littering GitHub with vanity projects that die on the vine. Think of all that wasted effort--like turning up the thermostat with the front door open.

When people ask about the choice of Common Lisp or Scheme, I ask which Scheme? No two are alike and none of them has achieved enough momentum to be truly practical. You have to pick one and pray your requirements don't exceed it. In CL, we invest in implementations--ABCL, ECL, Clasp--each one practical, each one mostly runs your code unchanged. Our spec is ancient by today's standards so old code just works. We innovate in our package ecosystem which gives users of whichever implementation access to fresh work. Compare to the whitewater churn of the NodeJS ecosystem. I have a Ghost blog that's a few years old and I can't update it as things have changed so much there is no practical way.

I'd like to file off the most common pain points in CL, spackle over obvious gaps in functionality, improve documentation, and polish the developer experience until nothing stands between Lisp and world domination. It's the once and future language. I have no qualifications for the job whatsoever except resentment of the status quo, a burning need for elegance, and lack of employment. It's a short resume, but the nice thing about being self-appointed is that no one has to want to hire you.


If you end up starting a blog, please share it on HN so I can find it :)


Additionally jump into LispWorks or Allegro Common Lisp, to enjoy how the survivors from Common Lisp days with a full blown IDE feels like.


You have an extremely deep understanding of the discipline of computer programming for someone who has only just started to learn!


How kind of you to say. I held off on learning programming for so long due to my visceral disgust with C/UNIX and ALGOL syntax. I just knew it was the wrong set of abstractions that have trapped us all in a grey hellscape of mediocrity and wasted potential. When I was a kid I turned turtles into a spirograph in Logo on an Apple II. It ruined me. I didn't know Logo was functional or Lisp-inspired.

When I found functional programming in Erlang I got SO excited. When I found Common Lisp, decades of pent-up frustration melted away and I knew I'd come home. I guess my Unix Hating led to some intuitive thinking over the years and I've been making up for lost time over the past couple of years and mapping what I knew I wanted to what has been there in CL all along.


“not get freaked out using some libs that are stable and haven't been updated in years”

In this vein, it’s great that decades-old Common Lisp books are still current. The paucity of Web content is not as bad as it seems: you can get a real book.


Yes, building anything non-trivial in CL will show the discerning programmer that there is nothing new under the sun.


Could you clarify what you mean by 'there is nothing new under the sun' when referring to programming with Common Lisp? Are you indicating that Lisp has already introduced many of the concepts and features that are now found in modern programming languages, implying that contemporary languages have largely assimilated Lisp's ideas and paradigms?

Your comment left me with a strange sentiment. I'm somewhat disheartening because it implies that learning Lisp might be redundant given that contemporary languages have already incorporated its best aspects.


> Are you indicating that Lisp has already introduced many of the concepts and features that are now found in modern programming languages, implying that contemporary languages have largely assimilated Lisp's ideas and paradigms?

Yes. See effect handlers for an example, which are making rounds around the programming world as of late. They are equivalent to the Lisp condition system, except formalized to work in strongly statically typed programming environments.

> I'm somewhat disheartening because it implies that learning Lisp might be redundant given that contemporary languages have already incorporated its best aspects.

Sort of, lots of things have thankfully trickled from Lisps to other languages (including whole languages like Julia). The pleasant feeling of conversing with the language and programming bit by bit in it is hard to replicate with things like LSPs, though, since the implementation is always running in the background and programming in it is based on mutating it until it contains the program you seek.


> They are equivalent to the Lisp condition system

Not exactly correct due to the lack of higher order effects (https://news.ycombinator.com/item?id=20513370), but the condition system is "good enough" for a lot of use cases


TIL. The way I understand it, that's because CL is not required to perform CPS on its code and no CL implementation exists that does it on its own.


> might be redundant

I'm also the kind of weirdo who thinks that the old and new testaments are baseline requirements for a human to be "literate" (along with Ovid and Aristophanes).

Despite these works having been recycled a large number of times, the originals still bear close scrutiny.

You'll never know what modern $proglang authors screwed up until you go back and read the fundamentals. For a concrete example, consider the Python module system, and the pirouettes you have to do to use it programmatically. Until you look at (just for example) how this works in CL and RS5/6S, you'll not have the context to readily apprehend the hack job in question (although if you have a modicum of taste you may wrinkle your nose regardless).

It'll also give you a good feel for what the true inventions have been since the early nineties.


Could you summarize what is great about the CL module system ? How does it compare to other languages like Rust/Go ?


Runtime access to read and redefine namespaces and their contents at whim.

Similarly powerful to having the compiler available at runtime, another thing trickling very slowly into mainstream languages.

"But why would you want this?! That sounds dangerous!"

It's quite dangerous, but so is a 700cc dirt bike.

"Can you give me any practical reason you'd want this?"

Aside from the freedom to redefine other folks' code (see: the derogatory term "monkeypatching"), you can leverage runtime namespace access and manipulation to, when loading a "fasl" into a running Lisp image, identify all classes that you care about and then upgrade them to new definitions without restarting the Lisp process.

Most mainstream languages completely punt on "deployment" (PHP [accidentally] aside, where you can joyfully edit production all day, a paradigm too many folks sneer at in the name of cargo cult professionalism), forcing the system operator to restart the process with new binaries (or pycs, whatever, I'm sure you can generalize the point).

A CL system supports that model, of course, but one can also precompile binaries to be loaded into the running system, redefining system behavior without downtime. Binaries, we should note, that don't include the entire Lisp runtime.

It's not that this provides some sort of day-to-day omg ergonomic bonus over the popular Algol families, but that I feel respected as a software author in a Lisp context in a way that I really don't in many other contexts ("nobody needs generics", to pick on some old low-hanging fruit), and the language providing interfaces to itself in itself is a delightfully low-friction cognitive model.

To return to the dirt bike analogy, I could prattle all day about this, but unless you're the kind of person inclined to spend five hours a week learning to enjoy riding dirtbikes, I don't know that I can spill enough ink to convey the fun of managing a few hundred cc's around the track at speed. Countersteering, inertia management, traction, all of these things are going to go over your head if your idea of a fun time with motive power is a Rivian.


I've always said half-joking, that CL users write more text praising it than actual code in CL. This thread is a fine example, but still fun to read.

But back to your examples - I feel that a lot of ideas CL is built on just aged poorly, I guess? No way I want to see some magical binary where some guru adjusted a parameter in the image or the ugly head of monkeypatching or macros all over the place in production code. CL serves the myth of lonely hacker where the rest of the world has accepted the fact that writing software is mostly a social activity and that code is as much for other people as for the machine.


> I feel that a lot of ideas CL is built on just aged poorly, I guess?

byte-code enhancement in Java?

You'll find dynamic features in a bunch of other languages, too. R, Ruby, Python, JavaScript, ...


eyyy you should see the paens I've written to lovers past ;)

Note that I'm not strongly advocating anyone ship CL in hot loops in prod, merely that there are wonders in there worth examining, as perhaps the surviving mainstream descendant implementations missed a nuance or three.

It's really not a great fit for middle-of-the-road software teams with quotidien concerns.

It does have plenty of great food for thought for the thinking programmer.


> I'm somewhat disheartening because it implies that learning Lisp might be redundant given that contemporary languages have already incorporated its best aspects.

Indeed, Lisp is no longer as revelatory as it once was, since many innovations have been adopted, even surpassed in specific cases. Still, no other language gives you the total package. The environment is far more than the sum of its features list. CL just gets out of your way and allows you to think about problems not programming.


Except the little detail that JIT/AOT toolchains still aren't as widespread as they should be, with several languages offering only one variant, and there as still quite a few Lisp tricks either left behind in modern IDEs, or with various kinds of support depending on the language, like accessing everything across language and runtime, hot code reloading, time travel debugging.

Ah, and most relevant, many folks still believe that writing OSes in what was once a macro Assember for PDP-11 is the only way.


Is Common Lisp the first kitchen sink language (ala C++)? It always seems to me like there's a lot of "Oh yeah, Common Lisp has a feature for this, too"-type blog posts, though very little about actual usage of it.


Common Lisp is the uniformization of what Lisp Machines across Genera, TI, and Xerox PARC (although Interlisp-D was a different species) were offering for a full graphics workstation, alongside other systems.

It was as kitchen sink as having POSIX (basically a full blown UNIX specification), to be expected to fill in the stuff missing from ISO C standard library.


It's a rather subjective assessment. ALGOL 68 and PL/I were apparently considered rather "kitchen sink" by the standards of their time.




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

Search: