Hacker Newsnew | past | comments | ask | show | jobs | submitlogin
Practical Common Lisp (2009) (gigamonkeys.com)
144 points by tosh on July 30, 2018 | hide | past | favorite | 84 comments


https://github.com/pcl-ru/pcl-ru - Russian translation of the book, if anybody interested


Can anyone please comment what advantages and short-comings Common LISP has over "modern LISP" (i.e. Clojure)?


I love Common Lisp (and I miss its frequent discussions on HN a decade ago). That trend seems to be resurfacing now.

The main advantage of Common Lisp is standardization. There's very few languages that can run old code across so many platforms and implementations. But that also holds the language back a bit in terms of progress. Libraries feel like they have been falling a bit behind. It's also one of the few practical languages that has a great multi-paradigm support.

The main advantage of Clojure is implementing lots of modern functional programming ideas. It's main disadvantage is being excessively tied to the JVM. That allowed initial rapid growth, but it brings some side-effects such as ugly stack traces. Aside from JavaScript, there are no alternative platforms.

Racket will now run on top of Chez Scheme. That's another major Lisp worth considering.

Lastly, Julia shows some of the multiple dispatch ideas in CLOS can lead to really efficient code generation when paired with LLVM or a similar backend.

I wish someone put all these interesting ideas in a blender and implemented them on top of Common Lisp, or as a new language. Something functional, with great typing (like Shen), static analysis and an LLVM-like backend are really worth pursuing. We need better languages to address modern challenges, like merging symbolic and probabilistic AI.


Common Lisp advantages:

- "more interactive" resumable exceptions, better code reloading functionality

- multiple values (cumbersome to use for superficial reasons, but with much better semantics than in e.g. scheme, for sure and probably also Lua)

- compiler macros

- readtable macros (although the design leaves to be desired)

- more sophisticated pretty printer

- more "target platforms"; with clojure it's basically jvm or, to a lesser extent, JS. If you dislike both you're out of luck

- there's a treasure trove of old but interesting material that uses Common Lisp (like PAIP by Norvig)

- much nicer for writing highly imperative code

Common Lisp disadvantages:

- Common Lisp has no eco system to speak off (but at least there's less churn than in clojure land)

- the language is frozen/dead

- career suicide, even compared to clojure

- no high quality free implementations (for example sbcl has a rather poor garbage collector compared to java, go and other widely used languages)

- single precision is de-facto the default floating point type

- built around cons cells which are a terrible data structure: slow, cumbersome and brittle; there's OK support for vectors and slightly worse support for multidimensional arrays and hash-tables and very poor support for generic collection manipulation or efficient functional data structures

- no out of the box support for laziness

- no concurrency or parallelism abstractions

- no good standard pattern matching library

- brain drain: very few smart people doing interesting things left (most left by the 90ies)

- often feels plodding and cumbersome compared to clojure (destructuring, basic string or sequence operations)


You have a lot of very wrong opinions on Common Lisp.

> multiple values (cumbersome to use for superficial reasons

How so?

> Common Lisp has no eco system to speak off

False. https://www.quicklisp.org/beta/ https://cliki.net/

> the language is frozen/dead

False. Extensions are being done through libraries and in the implementations.

> career suicide, even compared to clojure

I only ever get positive attention for having been a professional Common Lisp (and Scheme) programmer. Never had trouble finding freelance or full-time work.

> no high quality free implementations (for example sbcl has a rather poor garbage collector compared to java, go and other widely used languages)

How do you go from "SBCL's GC is slower than Java's" (which is a nonsensical statement, because different JVMs have different GCs), to "no high-quality free implementations?" There are three Free Software ones to choose from: SBCL, CCL, and ECL.

> single precision is de-facto the default floating point type

False. Where do you even pick up a ridiculous idea like that? http://www.lispworks.com/documentation/HyperSpec/Body/t_shor...

> built around cons cells which are a terrible data structure

Ignoring how dumb labeling the linked list a "terrible data structure" is, you do realize that data structures are something that you implement in a language, not something that you have to build into one? There are libraries for all kinds of data structures.

> no out of the box support for laziness

There is no reason to make it built in at the language level when you have closures.

> no concurrency or parallelism abstractions

That is handled by implementations and libraries.

> no good standard pattern matching library

Just a bunch of really great ones.

> brain drain: very few smart people doing interesting things left (most left by the 90ies)

You don't seem to know anything or anyone from the Common Lisp world. The best techniques for using the language (advanced use of closures, read macros, the condition system, and applications to domain-specific languages and transpilation) have only started coming out in the past 15 years, from people like Edi Weitz, Doug Hoyte, and Tobias Rittweiler. The only thing relevant from the 1990s is Paul Graham's On Lisp. Norvig's PAIP is very overrated.


Wrt brain drain: Edi Weitz is a smart guy, but not in the same league as e.g. Henry Baker, Jeffrey Mark Siskind, David Moon, Bill Gosper, Guy Steele, Marvin Minsky, Peter Norvig etc. There are still several people worth following left, even if you're not a common lisp fanboi (e.g. paul khuong), but hardly a lot which are moving the state of computing forward to the extent it was the case for many decades.

Dough Hoyte wrote Let over Lambda which is strong on the same breathless prose gracing its blurp:

> Let Over Lambda is one of the most hardcore computer programming books out there. Starting with the fundamentals, it describes the most advanced features of the most advanced language:

but IMO much weaker than PAIP on the actual "hardcore" programming. I can see why it would appeal to you more.

Wrt single precision being the default floating point type in practice:

   * *read-default-float-format*
   SINGLE-FLOAT
   * (cos 355/113) 
   -1.0 
   * (cos (/ 355 113.0))
   -1.0
   * (cos (/ 355 113d0))  ; get a more accurate answer by using double float literal
   -0.9999999999999645d0
The only practical implementation which diverged from this for some time was CCL (which used doubles), and they gave up because of friction with the rest of the community. I don't have time to address all your other claims that what I wrote is ridiculously false right now, so if it makes you feel better you can tell yourself that you still showed me wrong on 9 out of 11 points.


> Wrt single precision being the default floating point type in practice:

The only relevant example in your list is the fact that irrational functions coerce rational arguments to single floats (http://clhs.lisp.se/Body/12_acc.htm). Why do you think that dividing an integer by a single float should produce a double float?


Well, at least I appear to have got half the point across so let me spell out the other half: unlike all mainstream languages (apart from Fortran, depending on your definition of mainstream) Common Lisp interprets "normal" floating point literals as single precision floats. Technically the standard does not specify this, but in practice all implementations do it this way these days. To be precise the standard says that SINGLE-FLOAT is the default, but implementing SINGLE-FLOAT as IEEE double precision float is allowable, because only a minimum precision and exponent size are suggested for each float type. CCL used to do this, using SHORT-FLOAT for IEEE single precision floats and, IIRC, both SINGLE-FLOAT and DOUBLE-FLOAT for double precision.

If you don't want this (and I almost never do), you either need to explicitly specify that you want double precision, using d as an exponent marker in your literals or ensure READ-DEFAULT-FLOAT-FORMAT is bound to DOUBLE-FLOAT (not it's default SINGLE-FLOAT) when you read them (which can go wrong more easily, so the former is typically better). The coercion problem is harder to work around.

So in the light of these facts what exactly is false and ridiculous about my claim that single precision floats are the default floating point type in practice in Common Lisp?


You don't understand what IEEE floating point numbers are.

IEEE 754 specifies a binary representation. There is no "normal" floating point literal notation. There are numbers in decimal or scientific notation, nothing about which will tell you what kind of internal representation the number should have (not even whether it should be floating point or not). You have to add syntax extensions (this is how Common Lisp does it: http://clhs.lisp.se/Body/02_cbb.htm) or pick an arbitrary representation. Is setting a variable to pick a representation more troublesome or "can go wrong more easily" that setting any other kind of variable for you, or is there something special about that particular variable? You might as well complain that the default Common Lisp reader/printer does not handle C99 hex float literals.


> You have to add syntax extensions (this is how Common Lisp does it: http://clhs.lisp.se/Body/02_cbb.htm) or pick an arbitrary representation.

If an unadorned numeric token such as 2.7 generates a single-precision float (32 bit IEEE 754), that is a problem. There are good justifications why the default representation should be the 64 bit double.

The 32 bit single precision has too small a range and precision for general use; it is suitable mainly for saving space in large arrays.

We shouldn't have to use an extra suffix to indicate that we don't want that type, but rather the common double.

Even C gets this right.


>>The best techniques for using the language

That is good, but availability of Libraries are better.

Let's face it. Most people can't use even basic programming features well. When programming went mainstream we traded art for plumbing. What matters now is being able to talk to http end points, working with databases, message buses and myriad of other backend interfaces.

To have widespread adoption you have to do library stuff well, and then may be other features will matter.

Perl is an interesting use case in this regards. The language is very 'functional programming language like' in nature but has a very practical focus for common people, while providing the powerful features for advanced users.


(Mostly talking about SBCL)

Mature native code compilation (including runtime assembler), type declarations that lead to optimizations in generated code, SBCL compiler can use declared types for compile-time type checks, read/compiler macros, extremely easy interface to C (JNI is a pain in the ass), multi-paradigm and doesn't prematurely optimize like Clojure (I have no need for STM or immutable data structures in 99% of the things I do), very powerful and flexible debugger, CLOS, more sophisticated interactive development.

Clojure is a no-go for me primarily because it's tied to the JVM (or worse, Javascript) and the Java ecosystem but also because it prematurely optimizes with the sort of decisions it has made in the design space (immutability, STM, crippled reader, not Lispy-enough debugging and so on).

Common Lisp is much more flexible in the sort of problems it allows you to explore and attack. Even the special features of Clojure (STM, immutable data structures) can be used in Common Lisp via existing libraries.

Finally, for me Lisp == Lisp Machines and Clojure is a big step back in that regard (and not really modern at all) since it de-emphasizes (by lacking things that Common Lisp has) the interactivity that made Genera so iconoclastic and powerful.


Clojure was my first lisp; I use it every day at work, especially on JavaScript. ;) The (semi-)joke is that the reason Clojure targets the JVM and JS runtimes is so we can get paid to write it.

However, I have started playing with Lisp (SBCL) a bit in my off time and am finding it interesting. I have a desire to write some CLI applications and think Lisp might be a better fit than Clojure. I was drawn to it for a lot of the reasons you note:

1. Native compilation

2. Reader macros

3. C FFI

4. Debugger

But it's also a bit like learning how to write with my left hand; my brain has been warped by Clojure to eschew place-oriented development and reduce mutability to a pinpoint. Lisp is completely, unabashedly mutable (which gives a lot of room for improved performance vs. immutable) and I'm struggling with it.


One way to use Common Lisp with a more immutable flavour is to use the folio2 library.[0]

I have enjoyed exploring it a great deal. The project's readme, explains it better than I can:

"folio 2 is a collection of small libraries that provide support for functional idioms and data structures in Common Lisp and a common set of APIs for working with them.

"It's a direct descendant of the older and simpler folio library, with a greatly expanded and reorganized API, and support for more data structures and procedures.

[0] https://github.com/mikelevins/folio2


If you want to use functional (immutable) collections in Common Lisp, try FSet [0]. (It's QuickLisp-loadable.) FSet greatly expands the space of programs that can naturally be written in a functional style in CL.

[0] https://github.com/slburson/fset


I have looked heavily at FSet :). I'm at a cross roads now with whether I try and push the Lisp block into the Clojure hole in my brain, or whether I should try and learn Lisp as it is and it's idioms.

FSet is really cool and I'll probably end up using it in conjunction with https://github.com/fiddlerwoaroof/cl-edn


When learning new languages I think it's always beneficial to drink the kool-aid and go as full idiomatic as you can, then step back and reflect once you're competent enough. (That may take a while with CL if you're not doing it professionally, there's a lot in there! I'm still on my own plodding journey to CL competence...) My reasons are for the personal side that doing so should help maximize the "learn new language to change how you think about programming"¹ side-effects and on the social side doing so should help understanding and interacting with other users and code in that community. It can be really frustrating to me when I read Python code written by a career Java developer who never bothered to understand what "Pythonic" is. I don't want to be that guy for someone else.

¹Incidentally this http://www.nhplace.com/kent/Papers/Condition-Handling-2001.h... is my favorite high-level article around one of those potentially mind blowing differences that is Lisp's condition system, written by someone who has spent a lot of time considering fundamentals of programming like what it means to be in an exceptional situation and how to handle it.


It is illuminating to read the output of such minds as Kent Pitman, Dan Weinreb, David Moon, Richard Gabriel, Rodney Brooks, Guy Steele and what they have to say about Common Lisp and the standardization process, in places such as comp.lang.lisp (the entire archive can be found online) and elsewhere. The depth and clarity of thought, the foresight, the attention to detail and the guiding principle of trying to find the "right" vs the "easy" solution (this is beautifully illustrated in "worse is better" by RPG).

Compare and contrast with the designers of today's popular programming languages. There is something severely lacking in the minds behind PHP/Javascript/Python/Ruby and other languages where projected popular appeal and "easy" is the prime design consideration.

Are we worse off? I think so, not just for technical reasons, but because we have lost something greater that can not be easily put into words.


Yes, the evolution of Lisp was guided by brilliant people. I regret throwing out so many of my old Computer Science related books over the years (e.g. the Interlisp Reference Manual) because the few that remain remind me of the history of programming that has occupied so much of my life.

While being nostalgic about the early days of programming languages, I am not as pessimistic about the current state of the art. There were plenty of old programming languages that have faded away despite being important in their day (IBM's PL/1) and lots of work on new languages is pushing in interesting directions (Haskell, Idris).

Common Lisp has hung on and perhaps survivor bias has made it seem special, but to me, it is special. While working with it I feel that I am working with a language of the future from the past.

The landscape of calculation is much bigger than it was half a century ago, when I started programming. It is populated by an enormous ecosystem of processing units from tiny to enormous, embedded to networked across the planet, with single processors to high performance multiprocessors. All of this is controlled by programming and the programmers that wrestle the world of computing into submission. In this Hobbesian ecosystem some languages have prevailed for many years, like Java and C, others have survived even longer like FORTRAN and LISP, and some have simply proliferated rapidly, like Python, all for reasons that are complex and involve human abilities and computer architectures. Not all of them are beautiful, Lisp is, but all of them have a place even if only to reveal what does and doesn't work.


> mind blowing differences that is Lisp's condition system

It's similar mind-blowing when you think about this: that people wrote and used an operating system in the 70s/80s in Lisp, where the condition handling system was in full effect throughout the whole user experience (for example when using the file system, the network, the development environment, etc). With all the pros and cons...


I do like Clojure for some of its opinions (immutable by default, strong encouragement of functional programming, convenient abstractions for collections, tasteful syntactic sugar).

But, I too am not necessarily a huge fan of the ecosystems that Clojure is presently tied to. I'd love to see an SBCL implementation of Clojure or (especially) an LLVM based Clojure. I'm grateful when Clojure offers me an out from writing Java, but I also feel like I need to understand both the Java and the Clojure.

The Rackjure dialect of Racket is pretty nice by my estimation (adds some of the conveniences of Clojure, isn't tied to / the beneficiary of an ecosystem like the JVM or Javascript). But I suspect it would frustrate people who like aspects of the CL ecosystem like long-lived system images, and a very neutral disposition toward immutability and functional programming.


Clojure isn’t really worth anything without the jvm/js ecosystem.

It’s a glue language for assembling components, most of which are written in Java.

Timothy Baldridge wrote a python based language heavily inspired by clojure called Pixie. That gives a feel for what clojure is like without the ecosystem. Basically it means reinventing many many wheels.


I don’t know—there are certainly some interesting / different things in Clojure as compared to other lisps. Even if you can technically do much of what Clojure can do in other lisps, idiomatic Clojure has a feeling that is distinct from CL, Scheme, or even Racket.

If Clojure offers nothing without the large JVM/JS ecosystems, then we could say the same about most languages (that aren't the size of Java/JS).

If it’s that the distinct cocktail of features and ideas in the language aren’t worth anything, then I’d respectfully disagree. But at that point we’d simply be arguing aesthetic preferences.


Well that's the problem isn't it.

Any new language today has little to compete with existing languages in terms language features, the competition is in terms or the library ecosystems, online help and overall ecosystem. That takes time and money to build.

Given how large the Java ecosystem is, and what it took in terms of money and time to go there is not easy or even possible to match in any relative comparison.

So it makes sense to make sacrifices(if needed) to re use all that Java work that has been done and has been going on over the years.

Clojure is the best thing that has happened to Lisp in years.


Agreed, regarding the effort required to compete on completeness. However, somewhere in between “fad language of the week” and “let’s use nothing but C, because nothing will ever compete” I do think there’s room for new languages.

I’m old enough to remember a time when Java and JavaScript didn’t exist. They, for better or worse, climbed Mt. Improbable to become widespread. Ditto Python. I’d sure love to believe that a better set of languages could also climb that mountain.

Clojure has certainly gotten a leg up by grafting itself onto Java’s ecosystem, but as you point out, there are sacrifices (stack traces, and inability to support things like TCO for two). Ultimately, I’d love to see a combination of two things happen:

- More libraries that work across all implementations of Clojure

- At least one or two implementations of Clojure where the mismatch between the host platform and Clojure is minimized to useful effect.

On the last point, I’d love to see Clojure hosted on Common Lisp. I don’t hold much hope for this given the toxic reaction that many Common Lispers have for Clojure, but I can see benefits in both directions.

For Clojurists, CL has the benefit of running in lots of different contexts with some useful performance characteristics in some cases (SBCL, I’m looking at you). I also believe a Clojure-on-CL implementation would have the possibility of good stack traces.

For Common Lisp, rather than Clojure just being a mindshare theif, pure-Clojure libraries would become usable to the CL community.

Sadly, I think we’ve already achieved peak collaboration between the Clojure and CL communities. The people who liked what Clojure was about already enthusiastically left, and the remainder seem to mostly harbor sour grapes.


> Clojure is the best thing that has happened to Java in years.

FTFY


The community in clojure has been friendly in my experience.

Unfortunately they’ve moved to slack for their communication. The clojure group on google is pretty stagnant which is too bad. Topic threads are the exception in a chat room.

Conversations in a forum like groups can serve as their own documentation. Chat logs have to be deciphered.


There is Clojureverse


Common Lisp has the advantage of being much more stable in terms of language changes. You can pretty much take CL code from the last two decades and run it. It's also got a better native compilation story.

I like Clojure for bringing maps and vectors into first class language constructs with simple syntax and the fact that it runs on the JVM means I have access to the entire Java ecosystem of libraries and tools.


I wonder where the vector myth comes from. Common Lisp always had a vector data type and syntax for it. Just not [], but #(). Actually Common Lisp has multi-dimensional arrays, bitvectors, ...

Vector:

  CL-USER 102 > (map 'vector #'char-code "foobar")
  #(102 111 111 98 97 114)

  CL-USER 103 > (reduce #'+ #(102 111 111 98 97 114))
  633
Bitvector:

  CL-USER 105 > (reduce #'+ #*10101001010101)
  7
2d-Array

  CL-USER 106 > (aref #2a((1 2) (3 4)) 1 1)
  4


I didn't say CL has no vectors or syntax, just that in Clojure using sets, maps and vectors, is much more similar to using lists than it is in CL.


Common Lisp reads and writes vectors, has a simple syntax for them and provides a sequence abstraction over lists and vectors.


Operations that in Clojure require only changing the kind of brackets, in Common Lisp are more verbose and explicit. If you don’t see the clean syntax and simple utility in that then fair enough, but I do.


reading and reducing a list:

  CL-USER 109 > (reduce #'+ (read))
  (1 2 3)
  6
reading and reducing a vector:

  CL-USER 110 > (reduce #'+ (read))
  #(1 2 3)
  6
I've changed only the opening bracket.


Did you change from map to reduce because map needs to be told what kind of sequence to create? In Clojure the literal syntax avoids that. In addition there is this caveat about the literal vector syntax in Common Lisp:

You can use the #(...) syntax to include literal vectors in your code, but as the effects of modifying literal objects aren't defined, you should always use VECTOR or the more general function MAKE-ARRAY to create vectors you plan to modify.


The literal syntax has nothing to do with MAP. I can define a MAP which returns a vector or a list, depending on the input:

  CL-USER 111 > (defun maps (function sequence)
                  (map (type-of sequence) function sequence))
  MAPS

  CL-USER 112 > (maps #'1+ '(1 2 3))
  (2 3 4)

  CL-USER 113 > (maps #'1+ #(1 2 3))
  #(2 3 4)
I can also write a compiler macro, so that the implementation is chosen at compile time, when a literal data object is used... stuff which a Common Lisp implementation already might do for MAP.

Common Lisp chose to define the basic MAP to always specify the result sequence I want to create or NIL for no sequence.

> You can use the #(...) syntax to include literal vectors in your code, but as the effects of modifying literal objects aren't defined, you should always use VECTOR or the more general function MAKE-ARRAY to create vectors you plan to modify.

That's not different for lists and vectors. For both I need to know which I want to modify. Common Lisp has generic operations to create and copy sequences for that: I can call COPY-SEQ to copy the literal sequence or call MAKE-SEQUENCE to create the sequence type I want.

Here (and in many other places) Common Lisp is a low-level language, which exposes these things to the programmer.


So you admit the syntax is not as brief or as clean when you lose Clojure's data structure literals, and you have to write additional code for each type to get there?

You haven't even touched on sets, which in CL are not a built in type, even though there are functions that let you pretend a list is a set, it's not really the same thing when it's not an efficient underlying representation.

Personally I wouldn't call Common Lisp a low level language. A programmable language sure. Low level implies being close to the machine architecture, which it clearly is not.


> So you admit the syntax is not as brief or as clean when you lose Clojure's data structure literals, and you have to write additional code for each type to get there?

I didn't admit anything like that. I showed you that vectors are well integrated in Common Lisp.

> Personally I wouldn't call Common Lisp a low level language.

I wouldn't either.

But then I didn't call Common Lisp a low level language. I said: 'Here (and in many other places) Common Lisp is a low-level language' - which means that Common Lisp is PARTLY a low-level language - for example no detection of modification of literal data is required/provided - the programmer has to make sure data can be modified when necessary. A higher-level data-structure would probably require detecting this or would provide only immutable data types. Common Lisp has many relatively low-level features, from cons cells to directives telling the compiler to avoid runtime type checks. Having singly-linked lists is much lower-level than persistent sequences of Clojure.

Generally it's not surprising that Common Lisp has lots of low-level features, since it has been used to write much of itself and large parts of its runtime - where Clojure is a hosted language: large parts of the runtime, the compiler and parts of the library are not written in Clojure: https://github.com/clojure/clojure/tree/master/src/jvm/cloju...


I see what you mean. CL is low level in that it exposes implementation details of some of its data structures and let's you use them at that level. Sure that is powerful.


I don't know car/cdr/cadr/caar etc might not be close to the current machine architecture... But to me at least it feels quite low level. Perhaps similar to some of the unfortunate parts of the jvm/byte code spec that held back/convoluted dynamic languages on the jvm for a long while.


Well I guess car and cdr literally were CPU instructions back in the day, but it doesn't really make you a low level language in the way that C is. i.e: The entire language is really a thin wrapper over the assembly language which itself is an abstraction of the byte code. Most languages have arithmetic and more advanced mathematical operators that reflect actual op codes in the cpu, but that doesn't make those low level either.


The expectation is still that cons cells and the operators CAR and CDR can be implemented very efficiently. A CONS cell will on a typical machine be just two machine words - on a Lisp Machine it was sometimes just one machine word - when the cons cell was a part of a linear list.

If you look at an unsafe CAR operation, it is often just a single machine instruction.

Take this function which calls FOO twice. FOO is just calling CAR and inlining this call.

  (defun bar (a)
    (declare (optimize (speed 3) (safety 0) (debug 0))
             (type cons a)
             (inline foo))
    (the cons (foo (the cons (foo a)))))
The disassembly then shows that CAR is a MOV instruction:

  ; Size: 14 bytes. Origin: #x1002FB5E49
  ; 49:       488B40F9         MOV RAX, [RAX-7]                 ;  no-arg-parsing entry point
  ; 4D:       488B50F9         MOV RDX, [RAX-7]
  ; 51:       488BE5           MOV RSP, RBP
  ; 54:       F8               CLC
  ; 55:       5D               POP RBP
  ; 56:       C3               RET

For a language like Lisp this is low-level stuff: telling the compiler to optimize for speed, no runtime checks, provide no debug information, declare the type of a variable, declare return types and instruct the compiler to inline a function.

Or take structures in CL. They are defined such that can be compiled to a linear vector-like data structure with slot-access to known and static offsets. No redefinition, no meta-data information - low overhead.

Two of the original goals of Common Lisp were defined as this:

> Portability

> Common Lisp intentionally excludes features that cannot be implemented easily on a broad class of machines. On the one hand, features that are difficult or expensive to implement on hardware without special microcode are avoided or provided in a more abstract and efficiently implementable form. (Examples of this are the invisible forwarding pointers and locatives of Zetalisp. Some of the problems that they solve are addressed in different ways in Common Lisp.) On the other hand, features that are useful only on certain ``ordinary'' or ``commercial'' processors are avoided or made optional. (An example of this is the type declaration facility, which is useful in some implementations and completely ignored in others. Type declarations are completely optional and for correct programs affect only efficiency, not semantics.) Common Lisp is designed to make it easy to write programs that depend as little as possible on machine-specific characteristics, such as word length, while allowing some variety of implementation techniques.

> Efficiency

> Common Lisp has a number of features designed to facilitate the production of high-quality compiled code in those implementations whose developers care to invest effort in an optimizing compiler.


> If you don’t see the clean syntax and simple utility in that then fair enough, but I do.

I see a "cleaner syntax in the example" but the fragments of code you don't like, the literals, are something that you won't see in production code except for a few constants maybe. The difference in ~"characters to change" for switching from list to vector is far smaller than the amount of characters spent on this thread... (edit: even over the lifetime of a programmer)


It's not really about just saving characters, it's about the fact that vector, set and to some extent map can be used in Clojure code as if they are an equal citizen in the syntax to list. It saves valuable mental cycles that can be used on your actual problem instead.


I guess I should also mention on Clojure's side, the immutable by default data model and pattern matching.


I'd say pros:

  - Compiles to native
  - Superior REPL experience/interactivity
  - Easier C FFI
I'd say cons:

  - Smaller ecosystem, not as many libraries.
  - Dated tooling.
  - Inferior support for multi-threading/multi-core.
  - Not as homoiconic. Only lists are by default.
  - Standard is abandoned. No new versions since 1994. Some CDRs till 2013.
Neutral:

  - Leans towards OOP and imperative mutability over Functional and immutable. 
  - Doesn't have unified abstractions over datastructures.


Common Lisp compiles to much more than just 'native'. Like C, LLVM, DLLs, ... and a has a bunch of exotic stuff like whole-program-to-C compilers, Common Lisp on the metal or Common Lisp on top of Prolog https://github.com/TeamSPoon/wam_common_lisp .

Common Lisp also has full language interpreters: these are implementations of Common Lisp which interpret s-expression level code - not byte code.

Common Lisp can easily dump images and make them executable. Saving and starting them is quick.

Common Lisp has implementations mostly written in Common Lisp - including the compiler and large parts of the runtime.


Is this point of your parent commenter right?

> - Not as homoiconic. Only lists are by default.


I read that, too. But I'm not sure what he means by 'Only lists are by default'. If he means that only lists have a literal representation, then it would be slightly wrong - since Common Lisp has literal representations for various numbers, symbols, characters, strings, vectors, arrays, structures, pathnames, ...

Plus the s-expression syntax is user-extensible - making it as 'homoiconic' as the user needs it.


Interesting, thanks.


I'm referring to the fact that Common Lisp does not have a map and set literal by default. It has a lot of homoiconic literals, and is still very homoiconic. And Common Lisp's reader and printer can be extended by the user to add them back in non standard ways. I still personally consider it as a slight cons against Clojure.


Where do you get the "inferior support for multi-threading/multi-core" from? SBCL and ClozureCL (among others) support both with no issues.

Dated tooling? Not if you compare it to Clojure tooling, SLIME is lightyears ahead in terms of robustness. I've lost track of the Clojure REPLs (nrepl, CIDER, inf-clojure, ...) that still suck.

Re: "smaller ecosystem, not as many libraries", surely you are joking or not at all aware of things like the CMU Common Lisp repository. If we disregard Java libraries, Clojure doesn't even come close to CL code that has been made available over the last few _decades_.

One doesn't need CDRs or another (expensive, time consuming) standardization process when there are great portability libraries (FFI, threads, sockets, convenience macros, ..) or one can pick a defacto standard implementation that offers all of the above.


>I'd say cons:

I'll raise you a car.


I would add "- Better stack traces" to the pros.


One thing I tend to miss in Clojure is lack of TCO. Most people(like me), come to Lisp from SICP, The Little Schemer and PG's books. Where recursion is not just emphasized, you are actually trained to think recursively.

I found lack of TCO to be a little like a stick in the neck when it comes to both Clojure and Emacs Lisp. It prevents you from thinking in the way you have been thinking in other Lisps.

I understand Clojure depends on JVM and JVM doesn't do tail call eliminations yet, and Rich Hickey is right in thinking that method calls are too much a platform thing and we shouldn't be working around this limitation with a hack. However lack of TCO in Emacs Lisp is a little not easy to live with. From what I understand patches have been submitted to Emacs to make this happen but haven't made it to the upstream.

TCO is very important for any language that sells functional programming these days. Apart from the performance advantages, Recursion is a thought framework. Limitations are not easy to live with.

I hope some day Clojure and Emacs Lisp get TCO.

On a tangential note, JVM is a great platform, its a also a widely used platform, so changes are going to come in very slowly. So I'm not holding my breath waiting for TCO.


Most mainline Lisps either never did support TCO or only in some restricted way. The reason for that is that it clashes with other widely used language features in typical Lisps. There is a cost to supporting TCO - also a less nice debugging experience. Many CL compilers tend to support it with some restrictions - CL interpreters often not. Lisp Machines did not support it. CL on the JVM does not support it. https://0branch.com/notes/tco-cl.html

I started with Scheme and Lisp (Standard Lisp, ZetaLisp, CL, ...) and wrote/used TCO a lot. I tend to view the use of TCO for basic iteration as a mistake, which makes code harder to read. I tend to like either higher-order iterative constructs or powerful iteration constructs like CL's ITERATE: https://common-lisp.net/project/iterate/

I find the Clojure hack ugly.


>>I tend to view the use of TCO for basic iteration as a mistake, which makes code harder to read.

I understand some one like you is far more experienced and has been around for a long time. So obviously you know better than I ever will. And you are right. Even with very minimal Lisp exposure, after using Scheme and Common Lisp, Clojure's TCO thing feels unacceptable.

In fact after using Common Lisp, and I'm a novice, even some one like me feels Common Lisp to be a far superior language compared to other Lisps around.

The issue is, like I said, I started learning lisp starting with Scheme, and I started recently. First book was SICP, second was 'The Little Schemer', third was 'The Seasoned Schemer'. At that point your brain is hard programmed to think in terms of recursion, and whenever I see loops my mind moves to recursion automatically. You have to trust me when I say this, when I read PG's ANSI Common Lisp book , I skipped the sections on iterations and loops, because I thought I would never use them anyway.

I have to say those Schemer books do their job really well.

May be I just need more practice, so I will develop more stuff in Clojure, now that its not even possible to do that Clojure, eventually that TCO addiction should go away.

:)


The plain self-recursion part is not that great - especially one needs to write code in that TCO style. General TCO is a bit more - every tail call is a jump. I find it for example useful to have TCO code as low-level target for example for macros.

You have read a good selection of Scheme books. It's very useful to explore that style of programming and be able to really understand. Take it only as a hint, that there is more out there and that different styles and approaches exist. One can program with functions, one can program with linguistic abstractions, ... McCarthy called his paper: 'Recursive Functions of Symbolic Expressions and Their Computation by Machine' - this hints that one programs with recursive functions - but it enables also to compute with symbolic expressions - not only as data, but also as code. The evaluator as a recursive function, which implements Lisp.


What books would you recommend as further reading?


This twitter post from Peter Seibel about his book being translated into Clojure was hilarious: http://juliangamble.com/blog/2012/07/13/amazing-lisp-books-l...


Note that while this is one of the really good books on Common Lisp it is not new. The copyright is 2003-2005.


It's new for those who haven't read it. ;-)

There are a bunch of oldish Lisp books, but which might be read with some benefit even today. I'd would for example mention Performance and Evaluation of Lisp Systems (benchmarking Lisp), Keene's CLOS book, PAIP (writing classical non-trivial code and optimizing it), AMOP (meta-objects, extensible architecture), On Lisp (macros), Land of Lisp (fun&games), Common Lisp Recipes (Lisp lore), ... and also the Lisp Machine Manual ...

For a bunch of, often old, Lisp related books see my list: https://www.librarything.com/catalog/lispm&tag=lisp


In general, Lisp books were generally so far ahead of the state of the art at the time they were written, they remain very relevant and timely today.

And very likely tomorrow. :)


Any halfway good language has a constant procession of new books being cranked out about it. Reading a new "bible" is required every 3 to 5 years, along with updates to your entire code base.


Common Lisp Recipes by Edi Weitz is the most recent 'bible'. Another excellent book.


I strongly agree and I can't recommend it enough. It's not a good first book, though - it's not meant as an introduction to Common Lisp (for that it, in fact, recommends Practical Common Lisp). It's best read if you can already find your way around Common Lisp REPL, and write some basic programs - it systematizes the knowledge, explores various quirks of the language, and shows you practical solutions to common problems.

As for introduction, another nice book is Land of Lisp - it introduces you to most of Common Lisp by means of writing games of increasing complexity.


Has anything really changed, though? QuickLisp, I guess, but that feels like about it.


Also Lisp in a Box is no longer available. Nevertheless this book is always on my desk. I refer to it almost on a weekly basis.


It has been superseded by Portacle.

http://portacle.github.io/


I was going to mention ASDF but, from its changelog, it looks like its first release was in 2001(!).


This is a great book, but the subject of dealing with mp3s looks painfully dated.


Sad that this is true, though. It's "dated" not because mp3 format is dated, but because people no longer maintain their own music collection - outsourcing it instead to legit (e.g. Spotify) and illicit (e.g. YouTube) SaaS businesses. Which do not guarantee you that the song you bookmark today will be there the next year (as I myself experienced many times on both Spotify and YouTube) - but I guess songs get missing randomly and slowly enough that people don't notice.


The point of it is to discuss handling and parsing binary data. You could replace it with any binary format you want and have similar pedagogic value. FLAC, Vorbis, h.264, Matroska, etc.


Yes, that's a good point. Many binary file formats will also be quite complex, so it makes sense to restrict oneself to a useful subset of the data in a file. Otherwise one would be busy to explain complicated data representation specifics.

Another example could metadata in image (jpg, ...) files for example.


I wonder what would be a great replacement topic nowadays. Ruby on Rails era was the blog in 5 minutes. What is "the project" now?


For front-end the community seems to have landed todo lists as "the project".


Todo lists lack so much basic app functionality. They are only low-hanging fruit for a common article pattern.


Building a data pipeline? Some kind of CV problem? Async chat server?


async IRC with ring buffers was one of my last school C project. It was really interesting and straightforward, and despite its "obviousness", it still made me understand better some C and unix key principles. I think it's a great idea for discovering a new language.


The chat bot?

Blockchain in five minutes?


Chat bots have been in Lisp textbooks since 1960s, kinda dated too!


I want to learn Clojure but every time I try I get bogged down thinking I need to learn Common Lisp first. I’m sure that’s silly but it seems like it would be important. Am I wrong?


Common Lisp is a very different language, I doubt it will help you to learn Clojure. Clojure is a smaller language than Common Lisp in a lot of ways, so learning it first, then Common Lisp later might actually be the faster approach.


Just learn Clojure. Common Lisp is mainly obsolete.




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

Search: