Common Data Structures in Common Lisp
blog.djhaskin.com
blog.djhaskin.com
The tl;dr is it's metaprogramming made extremely convenient. In other languages metaprogramming might involve regex to parse string representations of code, perhaps string interpolation, and eventually pass strings to eval() or similar. But since lisp code is its own data structure (not just a string as in other languages), the ways of interacting with and altering lisp code programatically are every bit as robust as the methods for altering any other data structure (like a list or array).
For obvious reasons, I'm proceeding with caution, as some practices are best exercised judiciously or infrequently (or never e.g. ruby continuations).
But my early thoughts are that now that I know of this concept it seems odd that it's not feature of every programming language. I guess languages can still be highly productive without metaprogramming as a first class citizen. And, funnily, it's probably a good thing if tools like metaprogramming (and properties like homoiconicity) are a not obvious first choices for solving problems.
I think it's quite nice if you're building tooling though. Like creating a web framework to write web apps, or translating business queries to SQL.
Writing imperative code always felt like transporting buckets of water. But CL (and other functional languages) feels like building a pipe system. Much more knowledge required upfront, but getting nicer the further you progress.
ADDED:
That's why there are so many frameworks, especially in the web dev world. It's nice to have someone carry the buckets to your room, even though you need to get it inside. The trouble starts when you want water somewhere not serviced. You have to figure the service routes to take it of at the most convenient place.
We can ask ourselves questions: what should it do? how should it work? what kind of features would it need to provide? From what parts would we like to build it? How can we integrate it into the programming language we use? How can we make it fast? How can we deal with errors?
There is a question we haven't been asking yet: how do we want to write them down? What would be a compact notation, verifiable for a state machine?
That's something which one can ask very early in Lisp: How should my code look like? How can I make sure I understand the state machine itself (not the low-level programming) months, years later and make changes to it?
One way to answer that would be to add a syntax extension for state machines to Lisp. That would be embedded into the language. One can experiment with that, without the need to write too complex scanner&parser combinations. Just write the state machine as a list of state descriptions. A macro could transform it at compile-time into (efficient) low-level operations.
Common Lisp is largely abandonware at this point -- e.g. there's no standard way to open a TCP socket!
I switched because I was making command line tools and clojure's startup time is abysmal. Babashka doesn't have all the libraries and GraalVM's native image requires herculean effort to work even a little bit and spits out monstrously huge binaries.
Common Lisp is actually a lot more popular than I thought it was. I think perhaps it doesn't look that way, but it's very strong I assure you. Join us on the lisp Discord!
It is possible to create relatively small binaries out of the box with nearly instant startup time. There's plenty of libraries and a strong community. There's also a great number of implementations.
A lot of the things that are not standardized in the standard have been standardized via implementation agreement and libraries abstracting away different implementation differences, such as cffi and bordeaux-threads. I defy closure to have anything nearly as feature complete for parallelization as parallels, blackbird, and CL-async. Speaking of which, the C FFI story is strong enough with lisp that any major missing library or language feature usually has a wrapping list Library around that feature written in C. While it's not standard, all the major implementations have adopted gray streams and people use flexi streams to open sockets. It's the same kind of du jour standard that Clojure relies on.
The standard is flexible enough to have several different implementations on several different platforms. If I need to interface with jvm languages, I can use armed bear common lisp. If I need just a really strong implementation that works in most situations I can use CCL or sbcl. If I need to integrate heavily with C or C++ code I can use ecl or clasp.
It is clunkier because it takes the view that agreement between programmers is more important than clean syntax. The standard was created to unite the different lisp implementations instead of divide them. I value this view.
- it lacks a robust debugger (where you can step, redefine, continue…)
- it uses the GC of the host VM
- can’t save interpreter images
- is designed to be a hosted language, meaning many implementation details are left to the host VM (JVM or JS)
- type checking
- performance
Also, wouldn’t call CL abandonware, since it’s a standardized language. Abandonware by my definition is all the Python 2 or JS code that you can’t run on a current implementation.
If we end up in petty and unproductive nitpicking like that, then there's no standard way to open a TCP socket in Clojure either. Mostly because Clojure, unlike Common Lisp, does not have a standard to talk of.
* It had reader macros (I know, they're usually not a good idea, but I like the power).
* Didn't use its vector notation for anything else than vectors (e.g. in bindings). Petty, but this hurts my eyes.
* Had CL-like gradual typing, TCO and something more similar to CLOS.