Rendered at 19:38:21 GMT+0000 (Coordinated Universal Time) with Cloudflare Workers.
vindarel 22 hours ago [-]
Notes on CL:
- why nothing on the "compiler" line? Everytime you load a snippet or a file with SBCL, it compiles it (to machine code). There's also compile-file.
- interpreter: likewise, all code is compiled by default with SBCL, not interpreted, even in the REPL. To use the interpreter, we must do this: https://github.com/lisp-tips/lisp-tips/issues/52
- command line program: the racket cell shows the use of -e (eval), the same can be done with any CL implementation.
- since the string split line introduces cl-ppcre, one could mention cl-str :D (plug) (much terser join, trim, concat etc)
- ah ok, for dates and times, flattening a list, hash-table literals… we need more libraries.
- java interop: with LispWorks or ABCL (or other libraries)
my 2c
sinsudo 21 hours ago [-]
Since you are also commenting libraries, I think that FSet (1) for inmutable memory,and perhaps a comparison with clojure, and the quick-lisp package manager could be mentioned.
Hash-table literals are covered by (among others):
- Serapeum
- golden-utils
- rutils
- make-hash
Though now I'm wondering which libraries would make for a proper canonical extended core... asdf, uiop (comes with asdf, so naturally), alexandria, bordeaux-threads, cffi, cl-ppcre, str, local-time, trivia,... and maybe fset? (although I personally prefer Sycamore's naming conventions)
...maybe also fivem (although I personally prefer parachute) and hunchentoot.
aidenn0 15 hours ago [-]
> - ah ok, for dates and times
local-time has its limits (e.g. Gregorian only), but it does everything listed in this chart
> flattening a list
What? Isn't this[1] just fine (<s>)
> hash-table literals…
Since the chart is sbcl specific, this ugly mess would technically count; a more portable (but longer) version could be made similarly using #.:
I know that the purpose of the page is to compare syntax of common lisp, racket, clojure, and emacs lisp.
But some examples could be more idiomatic, for instance instead of
(defun add (a &rest b)
(if (null b)
a
(+ a (eval (cons '+ b)))))
One should avoid eval and use endp instead of null:
(defun add (a &rest b)
(if (endp b) a
(apply #'add (+ a (first b)) (rest b))))
aidenn0 15 hours ago [-]
The use of cl:eval alone is enough to make me believe that the CL column was never reviewed by an experienced CL programmer. I am now more suspicious of the other columns, which are languages I'm far less familiar with.
TacticalCoder 7 hours ago [-]
> I am now more suspicious of the other columns, which are languages I'm far less familiar with.
It's not bad but for Clojure for example it says "nil is like null in Java" but null in Java is not falsy.
And it also says that destructuring is "named parameters" but it's not so: it's just destructuring (and there are two examples of destructuring given: one for the "named parameters" which aren't named parameters, and one for "parallel assignment" of local variables).
Nothing bad but it's not possible to go into much details in such a table.
kscarlet 13 hours ago [-]
(defparameter *a* '(1 2 3))
(setf (car *a*) 3)
And this is undefined behavior because it mutates literal constant. I stopped reading further. The CL column is so bad.
vindarel 8 hours ago [-]
The trap is using quote, with the list operator there are no issues:
(defparameter *a* (list 1 2 3))
and of course, mutating top-level variables is bad style.
CodeArtisan 21 hours ago [-]
Shouldn't it be
(+ a (apply + b))
db48x 20 hours ago [-]
Almost. It should be (+ a (apply #'+ b)). Common Lisp is a Lisp-2, so a + in the argument position is assumed to be a variable named +, not the function named +, unless you specify otherwise.
gus_massa 6 hours ago [-]
No, the idea is to assume for this example that + only can be used with two arguments and define a new function that can be used with any number of arguments.
ludston 21 hours ago [-]
Worse: Using recursion in Common Lisp isn't idiomatic, given that CL doesn't guarantee tail-call optimisation in the specification.
dreamcompiler 19 hours ago [-]
Sigh. This again.
All major Common Lisps support tail call optimization with proper declarations, with the exception of ABCL because it runs on the JVM.
And those declarations are all identical or almost identical, so it's easy to write an implementation-specific macro to guarantee TCO if you need to do so.
Some algorithms are easiest to express and read with looping constructs. For those algorithms, use looping constructs. Other algorithms are easiest to express and read with recursion. For those, use recursion. You shouldn't be afraid of recursion just because ANSI doesn't say TCO is guaranteed. You should be afraid of it if your code needs to run on ABCL, but otherwise, recur on.
aidenn0 15 hours ago [-]
I think it is fair to say that the CL community is divided on whether or not relying on TCO is idiomatic.
I prefer to write my state-machines as transitioning with tail-calls, and I do get called for it. It's relatively easy to switch something written in that manner to using a loop with a trampoline, so I do so when my collaborators request it.
ludston 15 hours ago [-]
I wouldn't argue about things that are a matter of taste normally, except that I've had the experience where I've turned down optimizer settings in order to debug some code better and then the had stack overflow.
ludston 16 hours ago [-]
Sigh and yet it continues to be true. You can make a pragmatic decision and rely on tail call optimisation for your specific case, but if you are writing a CL library, then it is not idiomatic to use recursion in the same way that you would for Clojure or Scheme.
Even with SBCL, for example, it doesn't have tail-call optimisation for all architectures at all optimisation levels.
19 hours ago [-]
sinsudo 22 hours ago [-]
The page indicates that there is not function for documentation in common lisp, but
(documentation 'documentation 'function)
"Return the documentation string of Doc-Type for X, or NIL if none
exists.
System doc-types are VARIABLE, FUNCTION, STRUCTURE, TYPE, SETF, and T.
Also http://rosettacode.org for computer tasks implemented in many computer languages to allow you compare syntax and code.
dreamcompiler 18 hours ago [-]
Likewise apropos. It's an ANSI function.
dfox 11 hours ago [-]
> cannot start with digit
Not only it can, but both CL and Emacs Lisp actually defines primitives with names that start with digit.
kickingvegas 21 hours ago [-]
Perhaps related, I'm maintaining a "cheatsheet" to let Python programmers see what an Elisp equivalent to typical Python functions/methods are.
Are `(push s x)` and `(push x s)` correct for push and insert, resp.?
eamonnsullivan 23 hours ago [-]
Clojure 1.6, Emacs 24.5... These are pretty old versions, at least of those.
rahen 20 hours ago [-]
Emacs Lisp is a descendant of PDP-10 MAClisp, which makes it one of the oldest Lisp dialects still actively maintained. Whether it's version 24.5 or 30.2 doesn't make much of a difference semantically.
db48x 23 hours ago [-]
Most of the things in that table won’t change from version to version anyway.
devin 19 hours ago [-]
To be fair I think the only real differences since 1.6 you’d see are transducer versions of some of what’s in here for Clojure. The stuff expressed here is all very basic.
waffletower 2 hours ago [-]
There is a difference with the very first line item, though minor:
"clojure --version" is available. True that transducers aren't going to be a part of this comparison.
waffletower 2 hours ago [-]
I have been a clojurist for more than a decade and I started with Clojure 1.7. 1.6 was released in 2014!
aidenn0 15 hours ago [-]
CL list comprehension:
(loop for file across "ABCDEFGH"
nconc (loop for rank from 1 to 9
collect (format nil "~C~D" file rank)))
Jach 10 hours ago [-]
Or with more Python-esque syntax:
(let ((files (coerce "ABCDEFGH" 'list))
(ranks (loop for r from 1 to 9 collect r)))
[(format nil "~a~a" file rank) (file <- files) (rank <- ranks)])
Something I've been meaning to do is try putting together a cross-lisp package manager -- if only because it'd be fun. Maybe it would favor code that could be readily run or eval'd or maybe with some sort of clj/cljs type dynamic dispatch for anything implementation specific.
FergusArgyll 22 hours ago [-]
As someone who's not a programmer but has beginner - medium python & C skills. I'm in middle of learning lisp (elisp to be precise) and it feels like reading poetry. It's a transcendent experience that's hard to explain. Such beautiful concepts. Everything flows in a way it doesn't in C based langs
Would be interesting to see how Jank is coming along in this space as well.
veqq 20 hours ago [-]
Jank's just supposed to be Clojure with full compatibility, when mature.
FrustratedMonky 22 hours ago [-]
Nice comparison.
But makes me think we'd be better off if we all just focused on a single one, and grew it, made it better. Not having 4 versions of something almost identical. Fragmentation can hurt adoption.
Personally I prefer lisp 1 languages, like scheme. Even there, though, there was a split over r6rs, so we got a bunch of mostly-like-r5rs schemes and racket.
Maybe the problem is that lisps are no longer popular enough to have a winning implementation! If there is one, though, then it's Common Lisp on SBCL.
ludston 20 hours ago [-]
They are as different from one another as Java is from C# is from JavaScript.
FrustratedMonky 7 hours ago [-]
So, not by much?
erichocean 21 hours ago [-]
There are deep reasons for the variations, especially around (reader) macros.
- why nothing on the "compiler" line? Everytime you load a snippet or a file with SBCL, it compiles it (to machine code). There's also compile-file.
- interpreter: likewise, all code is compiled by default with SBCL, not interpreted, even in the REPL. To use the interpreter, we must do this: https://github.com/lisp-tips/lisp-tips/issues/52
- command line program: the racket cell shows the use of -e (eval), the same can be done with any CL implementation.
- since the string split line introduces cl-ppcre, one could mention cl-str :D (plug) (much terser join, trim, concat etc)
- ah ok, for dates and times, flattening a list, hash-table literals… we need more libraries.
- more files operations: https://lispcookbook.github.io/cl-cookbook/files.html
- emacs buffers: now compare with Lem buffers 8-)
- posix-getenv: I'd rather use uiop:getenv (comes in implementations).
- uiop:*command-line-arguments*
- exit: uiop:quit
- uiop:run-program (sync) / launch-program (async)
- java interop: with LispWorks or ABCL (or other libraries)
my 2c
(1) https://news.ycombinator.com/item?id=47779659
...maybe also fivem (although I personally prefer parachute) and hunchentoot.
local-time has its limits (e.g. Gregorian only), but it does everything listed in this chart
> flattening a list
What? Isn't this[1] just fine (<s>)
> hash-table literals…
Since the chart is sbcl specific, this ugly mess would technically count; a more portable (but longer) version could be made similarly using #.:
> java interop: with LispWorks or ABCL (or other libraries)I've had good luck with .net/java interop using FOIL (written by Rich Hickey prior to Clojure).
1:
=> (A B C D E F)It's not bad but for Clojure for example it says "nil is like null in Java" but null in Java is not falsy.
And it also says that destructuring is "named parameters" but it's not so: it's just destructuring (and there are two examples of destructuring given: one for the "named parameters" which aren't named parameters, and one for "parallel assignment" of local variables).
Nothing bad but it's not possible to go into much details in such a table.
All major Common Lisps support tail call optimization with proper declarations, with the exception of ABCL because it runs on the JVM.
And those declarations are all identical or almost identical, so it's easy to write an implementation-specific macro to guarantee TCO if you need to do so.
Some algorithms are easiest to express and read with looping constructs. For those algorithms, use looping constructs. Other algorithms are easiest to express and read with recursion. For those, use recursion. You shouldn't be afraid of recursion just because ANSI doesn't say TCO is guaranteed. You should be afraid of it if your code needs to run on ABCL, but otherwise, recur on.
I prefer to write my state-machines as transitioning with tail-calls, and I do get called for it. It's relatively easy to switch something written in that manner to using a loop with a trampoline, so I do so when my collaborators request it.
Even with SBCL, for example, it doesn't have tail-call optimisation for all architectures at all optimisation levels.
Not only it can, but both CL and Emacs Lisp actually defines primitives with names that start with digit.
https://kickingvegas.github.io/elisp-for-python/
Something I've been meaning to do is try putting together a cross-lisp package manager -- if only because it'd be fun. Maybe it would favor code that could be readily run or eval'd or maybe with some sort of clj/cljs type dynamic dispatch for anything implementation specific.
https://www.gnu.org/software/emacs/manual/html_mono/cl.html
But makes me think we'd be better off if we all just focused on a single one, and grew it, made it better. Not having 4 versions of something almost identical. Fragmentation can hurt adoption.
Personally I prefer lisp 1 languages, like scheme. Even there, though, there was a split over r6rs, so we got a bunch of mostly-like-r5rs schemes and racket.
Maybe the problem is that lisps are no longer popular enough to have a winning implementation! If there is one, though, then it's Common Lisp on SBCL.