(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))))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.
Even with SBCL, for example, it doesn't have tail-call optimisation for all architectures at all optimisation levels.
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.
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.
(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. (defparameter *a* (list 1 2 3))
and of course, mutating top-level variables is bad style.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.
(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.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.
- 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
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 #.:
#.(SB-IMPL::%STUFF-HASH-TABLE (MAKE-HASH-TABLE :TEST 'EQUAL) '((:X . :Y)))
> 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:
CL-USER> (let* ((result (cons nil nil))
(tail result))
(subst-if t
(constantly nil)
'(a ((b(c d)) e) f)
:key (lambda (x)
(when (and x (atom x)) (setf (cdr tail) (cons x nil)
tail (cdr tail)))))
(cdr result))
=> (A B C D E F) - 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.
(loop for file across "ABCDEFGH"
nconc (loop for rank from 1 to 9
collect (format nil "~C~D" file rank))) (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)])
Based on the list comprehension macro from 1991 in https://3e8.org/pub/scheme/doc/lisp-pointers/v4i2/p16-lapalm... that still works. (defmacro comp ((e &rest qs) l2)
(if (null qs) `(cons ,e ,l2) ; rule A
(let ((q1 (car qs))
(q (cdr qs)))
(if (not (eq (cadr q1) '<-)) ; a generator?
`(if ,q1 (comp (,e ,@q) ,l2) ,l2) ; rule B
(let ((v (car q1)) ; rule C
(l1 (third q1))
(h (gentemp "H-"))
(us (gentemp "US-"))
(us1 (gentemp "US1-")))
`(labels ((,h (,us) ; corresponds to a letrec
(if (null ,us) ,l2
(let ((,v (car ,us))
(,us1 (cdr ,us)))
(comp (,e ,@q) (,h ,us1))))))
(,h ,l1)))))))
(defun open-bracket (stream ch)
(do ((l nil)
(c (read stream t nil t)(read stream t nil t)))
((eq c '|]|) `(comp ,(reverse l) ()))
(push c l)))
(defun closing-bracket (stream ch) '|]|)
(set-macro-character #\[ #'open-bracket)
(set-macro-character #\] #'closing-bracket)
Supports filtering too, e.g. (let ((xs '(1 2 3 4))
(ys '(1 2 3 4)))
[(+ x y) (x <- xs) (y <- ys) (evenp x) (oddp y)])
; -> (3 5 5 7)The comments make me think this is ported from scheme, which has precise TCO rules.
[edit] macroexpanded:
(LABELS ((H-37 (US-38)
(IF (NULL US-38)
NIL
(LET ((FILE (CAR US-38)) (US1-39 (CDR US-38)))
(LABELS ((H-43 (US-44)
(IF (NULL US-44)
(H-37 US1-39)
(LET ((RANK (CAR US-44)) (US1-45 (CDR US-44)))
(CONS (FORMAT NIL "~a~a" FILE RANK) (H-43 US1-45))))))
(H-43 RANKS))))))
(H-37 FILES))Not only it can, but both CL and Emacs Lisp actually defines primitives with names that start with digit.