I keep flipping between Clojure and CL. I like functional programming, so I really like the workflow of Clojure, but the more-interactive nature of CL is incredibly appealing and I like that it doesn’t put so many constraints on you. I love how you can inspect everything and dig into the core of the language so easily and the interactive debugger is insanely cool.
But I just find it so painful to use, all the functions have strange names, docs are shaky especially for libraries, and I just keep bouncing off. I am going to try Advent of Code in CL this year, but I always get tied up in knots with the data manipulation, especially how you seemingly need to use the loop macro for basically everything since there aren’t that many data structure manipulation methods in the standard library. Hashes are also pretty awkward to work with compared to Java Maps or clojure maps.
Also, I can’t shake the feeling that doing all my data manipulation with linked lists is horribly slow, especially since they aren’t lazily evaluated.
ASDF and the package system is like no other language I’ve ever used, which always ties me in knots, too.
Does anyone have any tips? Is there something I’m missing regarding data manipulation, or is it more a matter of breaking through the pain barrier with practice?
Do you have some example code you’ve written that you feel uneasy about?
yeah I agree there’s a pain barrier… I don’t shy away from using libraries that make my life easier:
hash-tables: f* yes, I’m frustrated by their verbosity and that they don’t show their content on print. => use Serapeum’s
dict
andtoggle-pretty-print-hash-table
. https://github.com/ruricolist/serapeum/blob/master/REFERENCE.md#dict-rest-keys-and-values(dict :a 1 :b 2) ;; => is printed the same, so you can READ it back in.
I use
access
for a generic access to alists, plists, hash-tables, object slots… until I feel it’s too slow, but that’s not often, given I mostly do web. https://lispcookbook.github.io/cl-cookbook/data-structures.html#appendix-a---generic-and-nested-access-of-alists-plists-hash-tables-and-clos-slotsdata structure manipulation methods: see above link, hope it helps, and awesome-cl#data-structures for ideas. For instance, what about https://git.sr.ht/~fosskers/cl-transducers (“a “modern” API with map, filter, take, repeat, cycle, fold…”) (below under “Iteration”). I know I always paste those links but damn I wish I had them when starting ;)
doc
for the official one: https://cl-community-spec.github.io/pages/index.html (interactive search, oh my!) and novaspec.org/ (not open-sourced, waiting if possible)
for libraries, I’ll say that today we can afford the luxury to ignore libraries with bad doc, we’ll find another with decent ones. Let’s find examples?
You can keep the same ASDF snippets between projects and be done with it.
Welcome to Discord to rant freely https://discord.gg/hhk46CE
DO MAP
?
Try dolist, dotimes, and do, before resorting to loop.
I’ve been coding in CL for 33 years, still learning every day, and maintain this codebase:
https://gitlab.common-lisp.net/gendl/gendl
I doubt you’ll find any use of loop in there.
Regarding “linked list” structures and performance — if you feel your application has bottlenecks due to use of list, try confirming that by learning and using your implementation’s profiling tools, then replace with arrays, hash tables, etc where applicable.
Lazily evaluated structures are available in libraries and can be built for CL where needed (e.g. the sequences in above gendl library).
Why, out of interest? Is your issue that loop is too complex?
Thank you, this is very sage and practical advice. One question: what is so good about lisp? Why do you use it? You seem to be using it for industrial applications which I find very interesting
Well, for whatever reason, I’d never really learned loop, then I read Paul Graham’s ANSI Common Lisp where he says “for [reasons] the use of loop cannot be recommended,” and henceforth have used that as an excuse for myself never to learn it up to this day.
“What is so good about lisp” – well first of all I’m not sure exactly what or whom you’re replying to here - I don’t see where my original comment mentioned anything about Lisp being “so good” or whatever kind of straw man you are attempting to assemble here. But since you’ve asked: if you were to press me to justify my choice of Common Lisp for gendl, I would say I first came for the legacy codebases. I happened to know of legacy industrial codebases (still relying on a Gendl-like system from the 1980s) where i felt we could add value. Switching them to an entirely different underlying language infrastructure would not, in my estimation, have added value (at least not within my capabilities). So that’s what brought me in. But then i stayed with CL for:
- ANSI standardization and stability. I regularly fire up 20 year old code and it just works. Customer applications in the KBE categories tend to run for decades; they need to know that they’ll be able to upgrade their OSs and implementations without massively breaking their applications.
- Strong open-source library support. I can see where the loosely coordinated world of CL open-source could be daunting for a newbie individual coming in and trying to use raw Lisp. As a vendor of a CL-based system, I consider part of our value added to be the consistent, vetted, and configured Quicklisp/asdf snapshot we ship with our releases. Overall, I think the nature of the CL open-source library ecosystem provides a good balance with the rigid, frozen-in-time ANSI standard for the base language. And it’s a small enough community that you can actually get to know folks.
- Strong vendor support (Franz Inc in our case, who are about to celebrate their 40th anniversary).
Ths above items are justifications, but I think what really keeps me working in CL is more the subjective feeling of the whole environment — the interactive redefinition and test cycle, image-based development where you can fire up an image with everything loaded and “ready to go,” built-in debuggers, profilers, macroexpanders, introspectors, editor servers so you can slime into your running server to test, profile, debug, and hotpatch in-place. Stuff like that.
Oh I’m absolutely not setting you up for a strawman. I just like to hear why people use such a niche language, especially for 35 years. People tend to have pretty interesting reasons, I had a great conversation with a guy who built a startup on Clojure once for the same reason.
All of this sounds like an excellent reason to use CL, tbh. The standardization and portability is really impressive. I’ve heard that Franz are extremely solid as a vendor - so you use LispWorks as your runtime?
I think the interesting thing about the CL package ecosystem is that it doesn’t have the ‘move fast break stuff’ mindset of many other languages such as JavaScript. Some libraries like Bordeaux are essentially just finished and require very little work.
But on the other hand it’s really cool to see all these new attempts to modernize the language with new syntax, etc - that you can even do that is a testament to the language, I suppose.
Honestly, Im in the exact same position as you. I use Clojure professionally and I think it’s a great language but for personal stuff it feels too opinionated. That’s why I kinda prefer Ruby.
Common Lisp in my opinion is a complete dumpster of a language (and i hate the lisp-2 nature of it) but it’s sooo freeing and flexible.
What helps me is treating CL as a “build a world” language. Whatever I need, I just make. Nothings off the table. I don’t like standard functions? I write my own versions. It’s fun
I kinda wish Smalltalk was more text-oriented, it feels like a great “build a world” system too
Just use both langs What’s problem.
“all the functions have strange names”, very strange. Most names in CL standard are self-explanatory, such as DEF*, DEFINE-, MAKE-. Others have well-known history or legends or rules. Few are difficult, such as PRIN1 (still have meaning, think about PRIN2)
“progn” is a good example. This has far more sensible names in other Lisps. But I’m mostly talking about data structure access methods. They are so inconsistent - at lead the access library improves on this though.
Well known histories/legends/rules don’t count, I’m talking about new developer experience. But I get your point, they have a sort of consistency to them
PROG1, PROG2, …, PROGN very ruleful. N reminds you returning the last value. On the other hand, PROG means “Program Feature”. PROGN is really a good name.
“They are so inconsistent”. Different data structures have different operations. Specific operators are accurate and efficient. The costs are that you have to remember/command all these operators. A similarity is the equality operator. EQUALP is most generic. But you mostly use EQ, EQL, CHAR=, STRING=, etc. for specific objects.
Common Lisp is a bit like an onion. There are different layers of language and the inner layers date back to the first Lisp implementation from 1958. It’s grown over decades.
A beginner is now confronted with history and inconsistencies. On the positive side a goal was not to throw away useful older applications or libraries, they could be ported over the different generations of Lisp. Same for the book. If you read a CL introduction from the mid 80s it’s still mostly valid, but lacks newer stuff. It’s also the implementations: some SBCL code may date back to the early 80s, with Spice Lisp, which was started even before Common Lisp existed.
At that time PROG was a widely used control structure for imperative programming. PROG1, PROG2 and PROGN were a bit similar and indicate in the name which result is returned.
You walk in a street, where some house are a few hundred years old and some are relatively new, using different building styles.
I definitely prefer scheme’s
begin
, butprogn
does have a consistent logic behind its name.(progn expr1 expr2 ... exprN)
returns the result ofexprN
, and(prog1 expr1 expr2 ... exprN)
returns the result ofexpr1
. There’s also aprog2
which returns the obvious.There’s also a
prog2
which returns the obvious.It does? Strange. Because the spec says:
prog2 evaluates first-form, then second-form, and then forms, yielding as its only value the primary value yielded by first-form.
:)
Some people love CL and some don’t. You have tried Clojure. Maybe it is worth trying another lisp like LFE, Guile, or Racket:
Each has their own communities, development styles, strengths and weaknesses.
There are more lisps listed over at https://www.scheme.org
See what works for you.