Ask HN: What is the best way to learn Lisp in 2020?
I've been eyeing Lisp out of the corner of my eye for years. I know I should learn it and it will likely change the way I think about programming.
What is the best way to learn Lisp in 2020? I tend to learn best when I have a project to work on that plays to the strength of the language.
Some questions: - Should I start with Common Lisp, or some popular dialect (Clojure?) - What are the big patterns that I should be aware of? What is easier to do Lisp than is missing from functional languages like Python? - What are some big projects built in Lisp (other than this site)? - If it's so good, why isn't it more popular?
99 comments
[ 4.5 ms ] story [ 177 ms ] threadI found it way easier to setup and get going than CL & Clojure.
Don’t know what you mean you mean by patterns. Meta programming is way easier with Lisps. Python would not normally be classified as functional btw.
For that matter, Common Lisp isn't a functional language by the modern definition either.
This pretty much disqualifies it as a functional programming language.
Additionally, Guido and the community in general discourage functional programming. While the Common Lisp community already has a lot of libraries for FP, like FSET and others.
I recommend Practical Common Lisp - its free online: http://www.gigamonkeys.com/book/
1. The syntax: It's just parens/brackets, where the first item dictates the meaning of the rest, all separated by whitespace. That's the whole syntax of Lisps. If you have a good IDE (e.g. Emacs + CIDER), this can make you slightly faster at editing code than normal.
2. Functional programming: Many lisps traditionally use recursion. It's not necessarily a core part of Lisp nor is it super great (I avoid it whenever possible). Most of the FP you get from Lisp you can also get from JavaScript and a good FP library (e.g. lodash).
3. Immutability: This isn't traditionally part of Lisps either, mostly just Clojure, and it can be done with libraries in JS. But people often mix these up and think it's a Lisp thing so I listed it here.
4. Macros: Many other languages have good macros now (Elixir, Rust, etc). It can be useful in certain situations, especially how it's used in Rust's stdlib and third party libs, but it's not a panacea as commonly thought.
5. Homoiconicity: This is one of the biggest touted features of Lisps and I just really don't think it's that great. It's mostly useful in writing macros, but other languages have macro systems that don't really suffer from not having this.
6. Bootstrapping: The original idea of Lisp being a language that can bootstrap an interpreter of itself using only a tiny set of special-cased evaluation rules ("special forms") is intellectually neat but not practically useful.
7. Common Lisp: This is its own thing. It's super complex and some people really love it and swear by it. It's kind of like a C++ competitor. It honestly shouldn't even be categorized with the rest of the Lisp family. That's like saying Europe is part of Italy.
4. Macros: Many other languages have good macros now (Elixir, Rust, etc). It can be useful in certain situations, especially how it's used in Rust's stdlib and third party libs, but it's not a panacea as commonly thought.
For me, the big deal with lisp macros, is that you write the macros in lisp itself and can call lisp functions at compile time.
The last time I wrote a Rust Macro they only supported pattern matching and templating -- any rust code was only run by the final compiled program. Have Rust macros got that much more powerful now?
However, this is "just" a particularly well-polished form of compiler plugins - technically you can run C code at compile time with a GCC plugin, do cool things with libclang, etc. Writing project-specific compiler plugins is unusual but not totally unheard of: Linux has a couple, for instance.
The other of which is that they are so easy to write, that they just become another tool you use on a constant basis, just like you'd write a function, you write a macro, no big deal. I don't know that most Rust users have busted out a Rust macro or ever will.
And one more of which is that macros are so well integrated, the languages themselves are implemented almost entirely with them. The number of compiler forms you need is very small. I think Common Lisp has like 30 compiler forms, and Clojure like 20, Scheme must be even less. That's all you need. This is why Clojure has a JavaScript, C#, Erlang, C++ and Go dialect as well as the default Java one, because you don't have that much work to do in the compiler, everything else just re-use the macros.
I'll give in to Elixir, whose macros are pretty good, and for which I like the language, obviously, since it's like a Clojure with a different syntax and Beam instead of Java :p. Still would have preferred they'd just go with a full Lisp syntax and macro system, but oh well, a Lispyfied Ruby shall do.
But ya, most other languages, even those trying to have more macro support like Nim, Rust, Julia, don't fully approach Lisp macros, even if some gets pretty close, like Elixir. Also, I don't know if comparing other niche languages with a niche language is a very good overall comparison.
I'd also like to flip the question around: Why do these other languages bother with complicated syntaxes and then try and spend so much energy in trying to close the gap with Lisp Macros? Why don't they just go with a Lisp syntax from the get go?
I know the answer is they want non Lispers to use their language :p But seriously that's frustrating to a Lisper like me. Julia would be beautiful if it had a Lisp syntax.
It don't think it would. Lisp achieves its homoiconicity by using the same structure for data and code: the (possibly nested) list. This allows data and code to be manipulated in similar ways. Julia, unlike Lisp or APL, doesn't prescribe a preferred data structure (and shouldn't, considering its emphasis on performance).
Julia calls itself homoiconic, it uses Expr to model its AST and the parser returns it. There's even a show_expr method that prints it as an S-expression.
It just goes through great lengths to somehow have a textual representation that isn't s-expression based and then parse/print back and forth between the two.
Julia also has plenty of prescribed preferred data-structure, it has a standard Array with a Vector literal [1,2,3] which is heterogeneous and it has a standard Dict type as well.
So I'm seeing nothing that would be problematic if it had just gone with a Lisp syntax.
Yes.
However, the 99.9999% of Julia code out there isn't in s-expressions. Every Julia programmer doesn't use s-expressions. They use Julia syntax.
Thus, the task of writing Macros in Julia is harder than doing the same thing in Lisp.
>So I'm seeing nothing that would be problematic if it had just gone with a Lisp syntax.
They should've gone with a Lisp syntax, IMO. Beginners, who haven't passed through the chore of learning C-like syntax, find s-expressions as easy as any other kind of syntax.
I wonder if it's too late :p, maybe it can do what Racket does, and build on some per file #lang macro, give us proper Lisp syntax.
https://franz.com/downloads/clp/survey
http://www.lispworks.com/products/lispworks.html#personal
They are the surviving experience from what Interlisp-D/Lisp Machines were thought to be.
Or in case you go Clojure, get yourself a copy of Cursive,
https://cursive-ide.com/
[1] https://htdp.org
https://leanpub.com/progalgs/read#leanpub-auto-data-structur...
Or a book like Barski’s Land of Lisp
https://www.amazon.com/Land-Lisp-Learn-Program-Game/dp/15932...
I would start with Common Lisp.
Also it comes bundled with tons of useful libraries.
That said Steve Yegge "Emergency Elisp" is a great intro.
I feel like most people seem to gravitate towards Clojure afterwards for "real" projects - it definitely has the most real world adoption. The Java interop and JVM runtime feel like simultaneously Clojure's biggest strength and biggest weakness.
Here are some resources for both:
[Racket]
https://docs.racket-lang.org/guide/intro.html
https://htdp.org/
https://beautifulracket.com/
[Clojure]
https://www.maria.cloud/
https://www.braveclojure.com/clojure-for-the-brave-and-true/
1. Running on the JVM means that you can use Java libraries, but the syntax to invoke them is messy. The errors you get are also often rather opaque, and as someone who didn't know Java going in, I ended up having to learn how Java works as well to actually use them.
2. It produces Java artifacts. That means slow start up times, which is annoying for CLI tools. I know there's Graal, but last time I tried there were some issues using the stdlib CLI parsing library. You can also do ClojureScript, but it doesn't support everything Clojure does.
I like that Racket produces native executables, and having so much stuff built into the stdlib removes a lot of the "I need a database connection, what are people using for that now?" questions.
Clojure is not a dialect of Common Lisp. That would be like saying that C#, Go, Kotlin or Scala are dialects of Java. They are both Lisps, but neither is a dialect of one another.
I use Clojure at work and recreationally, and I learned Lisps first from it, so I am a bit biased, but I do think Clojure is a good Lisp to learn in 2020. I'd say Racket is a close second.
So my rec would be to learn Clojure, and this is my curriculum of choice:
1. Read through: https://kimh.github.io/clojure-by-example/#about 2. Then read through: https://aphyr.com/posts/301-clojure-from-the-ground-up-welco... 3. Then read through: https://clojure.org/reference/reader 4. Now start referring to the Cheatsheet: https://clojure.org/api/cheatsheet and https://clojuredocs.org and https://cljdoc.org. Bookmark them. 5. Finally, read through guides here: https://clojure.org/guides/getting_started 6. Now go to: https://4clojure.org and go through the exercises, start with elementary and easy. Even better, get the Android app, so you can practice on the go, the bus, the bathroom. Also for some reason the order of exercise in the Android app is the best, so I'd try and follow that. You don't need to go through them all, you can probably stop after easy or medium. 7. Once that's done, get Clojure Cookbook and go through the recipes. 8. Oh and, always go to the Clojure slack beginner channel and don't hesitate to ask questions and for help. Maybe always have it open in a tab: https://clojurians.slack.com/?redir=%2Fmessages%2Fbeginners%... 9. Now try to build something from scratch, like try to write a tic-tac-toe game, or write a search engine, or whatever you prefer.
> What is easier to do Lisp than is missing from functional languages like Python?
There are two kinds of Functional language. There are those who rely on first-class functions as their unit of abstraction. In those, functions close over their environment, and can be passed around as values. Python has support for this type of functional programming, but it is somewhat mediocre compared to most Lisps. Python still relies a lot on Classes and Objects, and there are some constraints with the way its functions are first-class especially around anonymous functions and partial application. Scheme and Racket are pros at this when it comes to Lisps.
And then there are those where data is immutable, and most functions are pure. In those, everything will be an expression that returns a value, instead of a statement which returns nothing. Recursion will be used for looping, because data can't mutate, so it must be passed along. Records will be value based, instead of identity based, because they are immutable. Clojure is the king here when it comes to Lisps.
Nothing will be easier to do, especially to you, someone unfamiliar with Lisps, and learning it. But it should be more fun and more rewarding. The development process is more interactive, Lisp languages tend to have every feature under the sun for you to learn and mess around with. Performance should be better than Python, especially if y...
It also has quite a lot of ideas that are useful outside Clojure, like its concepts around concurrency, state and immutability.
But if you want me to narrow down my recommendation to a single answer, it would be Common Lisp. It has one single comprehensive standard and there is a lot of real-world development done in Common Lisp. Scheme has a smaller standard, so while that is easier to pick up quickly, most Scheme implementations have a lot of extensions for all the stuff not in the small standard :p. So with Common Lisp, you have a much larger overlap between implementations.
For development, SBCL is great, as it is a free and open source implementation of Common Lisp which also has one of the best compilers to native code around, with well written code, you can get amazingly fast speed.
Practicall Common Lisp is a great resource for learning. It is very comprehensive, covering the important parts of the language and has some real-world examples. Land of Lisp is very nice, a bit more playful.
Common lisp is amazing as well. large and full of warts, but incredibly practical and battle-tested. if I were to write a larger project from scratch I would either pick SBCL or chez scheme, even though I myself am a guile weenie.
if I had access to Allegro CL or lispworks I doubt I would ever leave the comfort of them.
I ended up having to write some portable r6rs code, and that is easier in guile and chez, so that's why I went. I still feel a bit like I owe them to spread the word. Chicken is a very capable and plenty fast scheme. installing 3rd party software is a lot easier in chicken than any other scheme.
I would say that it is slightly faster than, or just as fast as guile3, but slower than chez.
If you keep wondering what's the best implementation, then you will suffer from analysis paralysis -- you'll end up spinning your wheels and doing nothing.
Flip a coin, pick something, and start building.
"How to Code: Simple Data" on edX, by Gregor Gregor Kiczales from the University of British Columbia.
https://courses.edx.org/courses/course-v1:UBCx+HtC1x+2T2017/...
The course teaches the Racket language, but proceeds at a rapid pace. It follows the "How to Design Programs" methodology developed in the 1990s (?) at MIT (and elsewhere). This is a test-driven-design method, AFAICT. (I think the MOOC author co-developed it while at MIT).
https://htdp.org/2003-09-26/
Someone has ported all the SICP exercises to Racket, so you can follow along with that book, too.
https://en.wikipedia.org/wiki/Structure_and_Interpretation_o...
There is a follow-up course, "How to Code: Complex data" but I didn't take it.