Does learning one Lisp help in learning the other?

2020-08-22 07:02发布

问题:

Is there any synergy between learning different Lisp languages? I'm currently learning Emacs Lisp, as it is immediately useful in my daily Emacs usage, however i'm fascinated with all Lisps, so maybe someday i will learn and use others. Will learning Emacs Lisp help me, when i start digging in Common Lisp, Scheme or Clojure? In other words, will it be for me like learning a completely new language, or some notions and paradigms are common? I'm also interested in comparison of unique differences between the Lisps, which will be a problem, when i come from one Lisp to another.

In How to go about learning Common Lisp and Emacs Lisp? it is mentioned that there will be "wastage", but it is not elaborated, to what degree.

回答1:

Yes - it is significantly easier to pick up a new Lisp if you know one already.

Reasons:

  • You'll understand the concept of "code is data" and metaprogramming in a homoiconic language (this is arguably the biggest single thing that makes Lisps unique / distinctive from other languages)
  • You'll be able to read Lisp syntax much more easily. It takes a few weeks to get to the point where you "see" the structure of Lisp code, but after that point it gets much easier. You start to ignore the parentheses after a while.
  • The development style is similar, interacting with a running environment using a REPL
  • They share common idioms, e.g. the use of lists and various functional programming techniques


回答2:

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:

  1. Common Lisp
  2. Scheme
  3. Clojure
  4. 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.