If you want to learn some older basics of Lisp, then Emacs Lisp is fine.
Emacs Lisp: can be used in Emacs. Development environment included. Older dialect of the mainstream Lisp. Lacks a lot of concepts. Has many extensions for editor programming. Common Lisp and Emacs Lisp share some direct heritage (naming, concepts, ...).
Common Lisp. Lots of good books to learn from. Lots of Lisp concepts (including OO programming) can be learned with Common Lisp. Highly recommended. Common Lisp has the most important Lisp facilities built-in and libraries provide the rest. There is a large amount of stuff which can be learned around it.
Scheme: different Lisp dialect created in the 70s. Not directly compatible with Emacs Lisp or Common Lisp. Lots of excellent books and other tutorial material. Highly recommended for learning Lisp basics and some more advanced things. The deeper you dig into Scheme the more it looks different from Emacs Lisp or even Common Lisp.
Clojure: very different Lisp dialect. Not compatible with Common Lisp, Emacs Lisp or Scheme. Shares some concepts, some concepts work differently. Good books. Recommended if you want to learn some Lisp or specially Clojure. Clojure puts emphasis on functional and concurrent programming - very relevant topics.
If you want to learn more mainstream Lisp (a Lisp that with a look and feel of a typical Lisp dialect), I would recommend Common Lisp or Scheme.
My language preference for learning Lisp (!) would be:
- Common Lisp
- Scheme
- Clojure
- Emacs Lisp
To give you an example:
This is the COLLAPSE
function from McCarthy's Lisp, written in 1960 (from the Lisp I Programmer's manual, 1960, page 101). It is basically what in many Lisp exercises is the FLATTEN
function. It takes a nested list and returns a new list with the atoms in a single list.
DEFINE
(((COLLAPSE,(LAMBDA,(L),(COND,
((ATOM,L),(CONS,L,NIL))
((NULL,(CDR,L)),
(COND,((ATOM,(CAR,L)),L),(T,(COLLAPSE,(CAR,L)))))
(T,(APPEND,(COLLAPSE,(CAR,L)),(COLLAPSE,(CDR,L)))))
))))))
This is the Common Lisp version. You can keep it in uppercase or convert it to lowercase. Both works.
(DEFUN COLLAPSE (L)
(COND
((ATOM L) (CONS L NIL))
((NULL (CDR L))
(COND ((ATOM (CAR L)) L)
(T (COLLAPSE (CAR L)))))
(T (APPEND (COLLAPSE (CAR L))
(COLLAPSE (CDR L))))))
It is basically the same. Only the form for defining functions has a different name and syntax. Otherwise the code is completely identical.
Try McCarthy's example in Common Lisp:
CL-USER > (COLLAPSE '(((A B) ((C))) ((D (E F)) (G) ((H)))))
(A B C D E F G H)
It runs.
Now let's try it in Emacs Lisp, using GNU Emacs. Emacs Lisp has lowercase identifiers:
ELISP> (defun collapse (l)
(cond
((atom l) (cons l nil))
((null (cdr l))
(cond ((atom (car l)) l)
(t (collapse (car l)))))
(t (append (collapse (car l))
(collapse (cdr l))))))
ELISP> (collapse '(((a b) ((c))) ((d (e f)) (g) ((h)))))
(a b c d e f g h)
It runs in Emacs Lisp without changes.
You can get similar versions going in Scheme (minor renamings):.
Here in Petite Chez Scheme:
> (define collapse
(lambda (l)
(cond
((atom? l) (cons l '()))
((null? (cdr l))
(cond ((atom? (car l)) l)
(else (collapse (car l)))))
(else (append (collapse (car l))
(collapse (cdr l)))))))
We can use DEFINE
to define a function. COND
looks slightly different. ()
is the empty list. Predicates have an ?
added.
> (collapse '(((a b) ((c))) ((d (e f)) (g) ((h)))))
(a b c d e f g h)
Runs.
In Clojure it would look different. Basically you have to rethink much of the code.
This is Clojure's own implementation of flatten
:
(defn flatten
[x]
(filter (complement sequential?)
(rest (tree-seq sequential? seq x))))
You can write a flatten
in spirit of the Lisp version - it would still look different.
From rosetta.org:
(defn flatten [coll]
(lazy-seq
(when-let [s (seq coll)]
(if (coll? (first s))
(concat (flatten (first s)) (flatten (rest s)))
(cons (first s) (flatten (rest s)))))))
Names are different, syntax is different, semantics is different (works on lazy sequences instead of lists).
Dialects like Common Lisp, Emacs Lisp, Visual Lisp, ISLISP and others try to keep the heritage.
Dialects like Scheme or Clojure felt not bound to the names and the syntax. They innovated in various directions. Scheme still provides direct versions of the old functionality. Clojure does not. Clojure programmers won't see this as an disadvantage.