If CL had only two namespaces, it would be odd and random, but not unique. It's like writing `./foo` vs `foo` in shell. Common Lisp is naturally more like operating system than programming language anyway.
But once you realize that CL has more than two namespaces (I think it has at least nine in the language itself, unlimited in user programs) it's easier to understand how it can be more than historical accident. People are used to thinking words in context.
I do mostly numerical programming today. Common Lisp would have been my preference and IMHO much better than Python I have no time to rewrite numpy or R libraries in CL.
Julia (runner up R) feel like the lispyest non-lisps to do serious numerical work in. Julia’s at the point where it has some best-in-class libraries as well as some frustrating blind spots.
With Julia I can almost pretend I’m using a straight-up lisp. The multiple dispatch in Julia is quite nice and is sort of “the big idea” that Julia uses to be so magical.
I’ve heard people talk about how similar Dylan and Julia are for a long time, but have never used Dylan myself. Are there any things that are standouts for Dylan as compared to Julia that are worth checking out?
I feel like Julia is pretty great, and has a good amount of inertia behind it in the domains that I frequent, but I’m curious about (Open)Dylan from the perspective of someone who’s used both.
I feel like Dylan is much more dynamic and OO than Julia. I don’t think there’s a way to have parameterised types (like generics in Java or C#) and a lot of the arguments for Julia’s type system are about being able to generate efficient code by being informed by the types. The parameterising complicates the subtyping relationships needed to specify how multiple dispatch works.
Sorry, maybe I'm misreading your comment, but are you saying that julia doesn't have parameterized types? If so, that's untrue, but maybe you're saying Dylan doesn't have parameterized types? It's a little unclear in the text.
> The parameterising complicates the subtyping relationships needed to specify how multiple dispatch works.
I don't think it adds all that much complication actually, and it adds a ton of extra expressive power. For instance, our arrays are parameterized by their element type and their dimensionality, so
Array{Int, 1}
is a 1D (vector) array of Ints whereas
Array{Complex{Float64}, 2}
is a 2D (matrix) of Complex Float64s. This is really important for making the most of multiple dispatch imo because I can write methods which take advantage of as much type information as possible. This isn't just helpful for performance, but also helpful for just expressing your algorithm in a clear and general way.
When I last wrote Julia I think it was unclear to me how subtyping worked with type parameters, or writing type specifiers for methods was complicated by them. I think all type constructors are invariant, so you couldn’t have T{X} < T{Y} unless X = Y (but I think you can have a rule like S{X} < T{X}). This makes sense in some cases as covariance or contravariance don’t work for arrays (suppose I want an Animal array but you give me a Dog array, then when I read elements I get Animals as expected but if I were to try to write a Cat there, it would be bad. Java has this rule and will throw a runtime extension in the case described), but it can work for immutable types, though Julia can’t enforce that all subtypes are immutable and type parameters needn’t be types anyway.
The standard Common Lisp namespaces I can count off the top of my head are variables/symbol macros, functions/macros, compiler macros, types/classes/condition types, class slots, TAGBODY tags, BLOCK names, THROW tags, restarts, documentation types. Users can easily define new ones.
I’m not sure I’d count compiler macros as a separate namespace to functions or macros. It feels like they’re the same to me. I think I also wouldn’t count throw tags as a namespace as they can be objects rather than symbols (but then again lists like (setf foo) are valid function names).
I think method combinations and packages are other namespaces.
Yes - thank you for the additional namespaces and about the THROW tag correction.
As for compiler macros, they do have their separate namespace, accessible via the `compiler-macro-function` function. They can be invoked instead of functions, but they exist completely on their own.
And types and classes are subtly different namespaces BTW. Try specializing a method on KEYWORD. In pure ANSI implementations you can't because KEYWORD is a type but not a class, and method specializers have to be classes.
Ignoring API compatibility and speed etc. (numcl doesn't talk to BLAS/LAPACK IIRC), even the basic datastructure isn't quite there. Numcl uses fixed contiguous lisp arrays as opposed to the strided ones that are standard in Numpy.
P.S- There are atleast 5-6 attempts to build something of the sort starting all the way back from Matlisp, and none of them are feature complete.
That was a wonderful comparison. Thank you. - I had some contact with Scheme twenty years ago, while working through SICP. As I recently made a deep dive into Emacs and Emacs Lisp, I started looking into Common Lisp as well. - I absolutely love programming in Lisp. And like you, Scheme‘s clean naming convention (`set!` for destructive operations, `foo->bar` for conversions and `string?`) were something I missed in other languages in the years to come (when I started learning Python was irritated that `data.sort()` was a destructive operation). Scheme‘s spec is so short, I could keep it all in my head.
Lambda calculus, list manipulation, list operations, closures, these are concepts I learned twenty years ago learning Scheme, and I‘ve seen these concepts slowly be adopted in other popular languages decades later.
Thank you for catching my interest in Scheme again! Will look into that in more detail. Was an old love of mine anyway. :)
Racket is great, and is now able to run on Chez Scheme as the backend. But as far as using it for production code, it's a bit problematic as the folks in charge are focused on it as a research and education tool first and foremost, meaning they are not shy about changing things a lot. Chicken compiles down to C and has a really friendly community with great docs and examples for "real world cases"
I tried scheme, but I couldn't live without a hashmap in the language. Thankfully, clojure specifies one as well as a uniform `seq` interface for collections.
The big problem is Clojure is a language without a published standard.
You might like S7, it has a lot of the high level features of Clojure in a scheme. (hashmaps, keywords, environments, Clojure/CL style macros.) This is what I am using these days.
I'm curious as to why they didn't mention Racket—perhaps they've just never heard of it.
My first programming language was Perl. After Perl came a little bit of C, but then I soon switched to Common Lisp. I wrote a lot of Common Lisp: I programed a text-based adventure game engine ala Zork entirely in CL! After reading Paul Graham's On Lisp and learning about Scheme, I took a look. My dad got me a PDF of SICP; I learned to prefer the Lisp-1 style of Scheme to the Lisp-2 style of CL.
I've done a bit of Clojure—I feel like Clojure does a better job of making data structures more ergonomic than either Scheme or CL.
I've recently started working more in Racket and I am loving it. It feels like a Scheme with a more visible, vibrant ecosystem. (Could just be me not being aware of the Scheme ecosystem—I was only 15 or so at the time I started writing Scheme.) Racket is definitely optimized for building DSLs. But I feel like the standard library is a bit better documented and easier to work with.
Just my experience though. I could be totally missing out on some great Scheme libs.
At the end of the day, Lisp in all its flavors is great, and I want to write as much of it as I can in my career.
FWIW, my first lisp was one called PLT Scheme. Most people know it as Racket nowadays.
There's been a couple decades' worth of divergent evolution since then, so that it's not actually compatible with any Scheme standard anymore. At least not by default -- there's still "#lang r5rs". In any case, one could be forgiven for thinking Racket is still just another flavor of Scheme.
I agree about Racket. While various lisps occupy specific niches, I feel like Racket is the lisp that checks the most boxes—especially for someone getting started and shopping around for the “right” lisp.
I prefer the syntax of CL over Scheme, but the latter is definitely a wonderful language and goes hand in hand with the wizard book, SICP.
At the end of the day, language choice, much like choice of editors or mechanical keyboards, is not that important — what is important is actually coding and building useful software. This can be done in any language, although I do not want to sound ignorant, as certain languages are better suited for certain tasks (eg swift for iOS dev).
As a Mac/Windows user, I’d be keen to know how popular Guile is for GNU Projects?
The ease of implementing call/cc early on when you have a trivial interpreter is dangerous - it becomes far too easy to end up not realising you've made a rod for your own back until way too late.
A spec entry for delimited continuations with call/cc as an -optional- feature would definitely seem like an improvement in terms of leading implementors down a saner path.
The fully-general continuations that call/cc creates require copying chunks of the stack to the heap for storage and back again when they're used. (Or pointing the stack pointer at the heap, which I find even more cringe-inducing.) They're memory hogs whose lifetimes are hard to reason about and they provide relatively little benefit to most programs, which is why Common Lisp doesn't provide them.
My main interest: delimited continuations are only valid within a specific stack extent so they're much for viable for extensive use - one could argue that async/await is kinda the simplest (and most limited) possible delimited continuation implementation.
They are faster, easier to reason about, less prone to leak memory and are always what you want. Everyone except maybe the chicken scheme people agree, but call/cc is free for them due to their GC strategy.
Nitpick: the latest standard is R7RS-small (https://small.r7rs.org), which is (expectedly) smaller than R6RS. I believe it was supposed to be accompanied by something like R7RS-large, covering the rest of what is expected in a Scheme standard to succeed R6RS.
Besides CFFI there's also cl-autowrap and claw. I haven't used claw myself, but I know cl-autowrap makes wrapping C libraries really easy. It took me about 4 hours and <200 lines of code to have a complete, usable binding for GDAL, and most of that was in convenience functions.
I'm curious to see what happens with the C++ support in claw. They're working on it, but I'm skeptical. I haven't had much luck using arbitrary C++ libraries from any language other than C++.
And I don't know what the author means by "somewhat rarer" in this context, but I personally use C libraries from CL all the time. Almost every CL project I work on, now that I think about it.
:claw author reporting in. At first, I was semi-skeptical too, but not anymore. I've managed to wrap a few C++ libraries (GLM, Filament, PhysX). Not without a manual intervention (claw is still evolving and adapting to C++ edge cases), but those libraries pretty much work now. I'm not sure I would be able to cover all C++ idiosyncrasies, but huge parts are already in.
That said, claw's C++ support is still in the works and I don't recommend diving into that mess yet. Stay tuned for Q1 2021 semi-usable alpha release. Or not. C++ is such a pile of shinies, I'm pretty sure I lost a few screws during :claw development.
Actually I was talking about how to bundle C code with a Common Lisp library (or system), so that ASDF could also handle building it.
Guile has a C API in libguile, so you can create C that's directly callable from Scheme with little overhead. You can write Scheme procedures in C and you don't need to gobthrough FFI.
As far as I know both languages have FFI. CL has CFFI, Guile has it in its standard library.
But can I actually ship some C code with my CL system and have ASDF take care of building it? If I want to ship CL bindings for a C library, as far as I've understood I need to tell users to install the library first. Maybe something like Clasp offers a C++ API?
Then again in both languages you can always use FFI and if things get hairy you can use grovelling.
With Guile, it's really easy to have a C library and package its guile bindings in the same place. Not saying you can't do this in CL, just that I don't know what it would look like to ship both C and CL in the same package.
Guile is an extended specific implementation of Scheme. Common Lisp OTOH is a language standard with widely different implementations. Just like Scheme has very different implementations - in sizes of small, medium and large.
Common Lisp with more extensive C extension support are CLISP (written in C, not that well supported nowadays), ECL ('embeddable Common Lisp', compiling to C), CLASP (integration with C++ via LLVM), mocl (commercial whole program compiler to C) and a bunch of others.
Thus the support of and integration into is different in Common Lisp implementations, just like it is different in Scheme implementations. GUILE was designed for C embedding, just like some Common Lisp implementations were designed for that task. Guile was also designated as a language of choice for the GNU project, which adds quite a bit community support - while Common Lisp (or one of its implementation) was not:
'Guile is the GNU Ubiquitous Intelligent Language for Extensions, and the official extension language of the GNU project.'
In Guile, because it lacks an optimizing native code compiler, they write C extensions just to get their code to perform acceptably. In Common Lisp the only reason you'd use CFFI is if you needed to call some code that was written in C.
> That said, Common Lisp is weird. What I find particularly jarring is that functions and variables live in different namespaces: if you put a function into a variable, you can’t just use it like a function, you have to funcall it.
Unless I’m misunderstanding, the author sounds like a developer that’s primarily used JS. It’s really not abnormal to have to include parens after a method name in a language, which indicating that you are calling a function.
> It’s really not abnormal to have to include parens after a method name in a language
That's not what he means. Assuming some pseudocode with a more common syntax to prune the parentheses question, Common lisp is doing this:
function f(x) { print(x) }
let a = f
funcall(a)(27) # prints 27
Whereas Scheme is doing this:
function f(x) { print(x) }
let a = f
a(27) # also prints 27
This is the fundamental difference between so-called Lisp-1 and Lisp-2 families, depending on whether functions and variable share the same namespace or if they have to be “lifted” from one to another through funcall.
In common Lisp you can call a regular function defined with defun as (foo "something"), but if bar is a variable containing a fuction you have to use (funcall bar " something"). I agree that this is weird and confusing.
The piece has the usual complaining about CL being a Lisp-2.
When I ask to have the advantage of a Lisp-1 explained to me, I get some handwaving about "elegance", rather than a specific explanation for why someone would want that. Personally, I find a Lisp-2 (or a Lisp-N) to be more convenient and more understandable. And I don't have to have lexical variables named "lst".
Saying that it's weird (which it is - I have a really hard time thinking of other languages that do this) and then pointing out that there are good reasons why it's that way, and linking an article explaining them, and then explicitly saying, "this isn't a fault, my comfort zones are a product of what I already know," isn't exactly complaining. It's possibly the most measured, non-partisan response to the whole lisp-1 vs lisp-2 thing I've ever seen.
Java is a Lisp-2; method names exist in a separate namespace from values (although, there may be semi-arbitrary restriction of name collisions): foo.toString doesn’t give you a “function object”.
The most basic reason for wanting a Lisp-1 would be to make code more context-free. "foo" refers to "foo", and you don't have to be aware of whatever special underlying context is there.
The closest parallel I can think of is Scala implicits. You give me a fragment of Scala code using implicits it might be hard to know _what_ is going to happen. Similarly, having a bunch of namespaces (or, perhaps more importantly, leaning into them and having many name overlaps) just means that it's harder to read a single code fragment and know what it's going to do.
Though I'm pretty partial to the argument that, like..... yeah you're going to call a function so the name refers to a function. But without extra symbols around this concept to help the reader, in this age it seems like a concept that is hard to take advantage of.
I don't know if it's a "reason" or not, but lisp-1 semantics would be more familiar to a language that legions of programmers would be familiar with - JavaScript. Eich wanted to make Scheme for the web, and having a variable containing a function, no matter where it's placed, would be the least cognitive distance from what people are used to.
> When I ask to have the advantage of a Lisp-1 explained to me, I get some handwaving about "elegance", rather than a specific explanation for why someone would want that.
It encourages programming more in a functional style. It's much easier to implement and use higher order functions, if functions are treated like any other object. In Common Lisp, functions defined with DEFUN are not, by default. Common Lisp supports the functional style, but in order to operate on functions with higher-order functions, you must first scoop them out of their special namespace with FUNCTION; and if you want to apply a function you have as a value you must FUNCALL it.
Lisp-1s remove these extra steps, and to some programmers that is more understandable.
> Clojure isn’t a true Lisp, or well, it is part of the Lisp family, but its extra syntax. Its ability to interface with the JVM makes it easy to leverage the thousands of JVM libraries out there.
I am often wishing I knew of an easier way to compare Lisps. I haven't used any dialect enough to fully grasp the subtleties. For example, the differences between Racket and Clojure.
Probably the main difference between Clojure and all the rest, from a purely lispy perspective, is that it's not list-oriented. Maps are its preferred data structure. I'm not even sure it has cons cells - at the very least, I've never seen them being used.
Probably the main thing that distinguishes Racket is that it's meant to be a playground for experimenting with programming language design.
Clojure is really designed around the sequence abstraction, which is list-like but not a list: Clojure’s various concrete datastructures all implement this abstraction and, consequently, much of clojure.core is more generic than the equivalent functions in the CL package: maps present as sequences of pairs; vectors, sets and lazy sequences all present as sequences of items, etc.
Clojure is oriented towards functional programming, hence no loops, but because it's also a thin abstraction over the JVM, it doesn't provide tail-call elimination, so you can't write traditional functional code the way you would in SML or Scheme. Instead, you have to use the clumsy "recur" construct to work around the JVM's limitations.
It's also less consistent than proper Lisps. For example, it's possible to produce a Java OverflowException using Clojure math operators. Some operations will gracefully upgrade to bignums, but others don't.
I am finishing up teaching an undergraduate-level course on programming language principles and paradigms.
Here is what I recommend:
1. Study Scheme for an introduction to programming in a Lisp-like language. I used Structure and Interpretation of Computer Programs as one of my course textbooks (I also assigned my students separate textbooks for the Prolog and Smalltalk portions of the class). My favorite part of this book is its discussion of the metacircular interpreter (i.e., a Scheme interpreter written in Scheme), which is key to understanding how a Lisp works internally.
2. Read the Lisp 1.5 Programmer's Manual. While this Lisp has long been succeeded by more modern Lisps, it is a very nicely designed manual that discusses the implementation of Lisp. It also has a short metacircular interpreter on page 13 that profoundly inspired Alan Kay and many other luminaries of computer science.
3. Begin learning Common Lisp. Common Lisp is much more complex than Scheme is; Common Lisp is to Scheme as C++ is to C. I started getting serious about learning Common Lisp earlier this year, and I am currently working through the Advent of Code exercises in Common Lisp. My favorite intro book is Common Lisp: A Gentle Introduction to Symbolic Computing. I also own a copy of Common Lisp Recipes, which has been valuable for learning Common Lisp idioms; part of the challenge of learning a large language is figuring out what are the most idiomatically correct ways of doing things.
4. Read The Art of the Metaobject Protocol. I have a copy of the book, but I haven't gone through it yet. My goal is to work through this book to have a full understanding of the Common Lisp Object System.
Regarding 4, I'd suggest reading Object-Oriented Programming: The CLOS Perspective first. It also has a great cover (same illustrator as AMOP), though it's not evident from Amazon's page last time I checked. This book is really a big collection of papers from several authors. It covers the topics of Lisp's OOP system and some of its history, its unique features (especially some comparison chapters with C++, Smalltalk, Eiffel), its meta-object protocol (and why you'd want it), some production reports from the field on its use in helping application development, and some implementation details to ward off "surely this is sooo much slower than a plain old function call?"
I really liked Practical Common Lisp by Peter Seibel ( https://gigamonkeys.com/book/ ) however, ignore the environment setup: the recommendations it makes are no longer maintained, these days Portacle is the best way to get started.
One thing I really like about this book is it assumes you know basic programming ideas, so it gets into distinctively “lispy” features relatively quickly. The chapters on exception handling and Common Lisp’s object system, in particular, really sort of blew my mind and had a fairly profound influence on the way I think about programming languages.
> I really liked Practical Common Lisp by Peter Seibel ( https://gigamonkeys.com/book/ ) however, ignore the environment setup: the recommendations it makes are no longer maintained, these days Portacle is the best way to get started.
A bunch of us from Freenode IRC have formed Common Lisp Programming Challenge (CLPC) at https://github.com/spxy/clpc in order to help beginners get started with Common Lisp (CL) while learning the language from Practical Common Lisp by Peter Seibel.
The outdated environment setup in the book can indeed be a genuine hurdle for a beginner. Portacle is a great way to get started. In the CLPC repository though, we are documenting the steps to set up Emacs, SLIME, etc. from scratch, so that a beginner to the CL ecosystem can try out and understand the traditional way of installing and setting up these tools.
If anyone here comes across this comment and is already reading or about to start reading the book Practical Common Lisp, or is eager to learn Common Lisp, you are welcome to visit https://github.com/spxy/clpc and join the Common Lisp Programming Challenge.
Hmm, maybe this isn’t the best for PLT: for that, I’d recommend the Art of the Metaobject Protocol which is a really interesting case study in the design of the implementation of a fairly complex part of the CL standard.
I was expecting Clojure vs Common Lisp. I love scheme but it's not fair to include considering how few libraries there are (I don't even know if there is a package manager).
I remember reading about Raven when it came out for Cisco Scheme. I totally forgot about chicken scheme. I just remember being interested in Guile and sad that only guildhall was available.
The article mentions GNU Guix as "the" package manager for Guile Scheme.
Now, Guix includes some 15000 non-Scheme packages, so it is somewhat "overkill". OTOH you can also install/run/dockerize a world of Common Lisp packages with Guix. :-)
But once you realize that CL has more than two namespaces (I think it has at least nine in the language itself, unlimited in user programs) it's easier to understand how it can be more than historical accident. People are used to thinking words in context.
I do mostly numerical programming today. Common Lisp would have been my preference and IMHO much better than Python I have no time to rewrite numpy or R libraries in CL.