Programing Language Design: Why Lisp Macro Sucks

,

in emacs lisp, there's dolist and dotimes, but they are annoying, because:

when you call describe-function, you see they are defined in Common Lisp package. But, actually, they are part of GNU emacs lisp (Iteration - GNU Emacs Lisp Reference Manual), it's just that the CL package overrides them for some reason. When a programer reads the elisp manual, then tries dotimes, then unpredictably, she sees the CL package. Huh?

And what's the difference between the two? It turns out it's very difficult to tell, unless you are a expert of Common Lisp. (i recall asking about it years ago, it has something to do with Common Lisp's form or such.)

so, first problem is the implicit overriding problem.

you can then start emacs by emacs -q then call describe-function again to see the default GNU emacs lisp definition.

Lisp Macros Complexity

then, second problem is that, you see they are defined as macros. COMPLEXITIALITY! Witness, from GNU Emacs 24.2.1:

(defmacro dolist (spec &rest body)
  "Loop over a list.
Evaluate BODY with VAR bound to each car from LIST, in turn.
Then evaluate RESULT to get return value, default nil.

\(fn (VAR LIST [RESULT]) BODY...)"
  (declare (indent 1) (debug ((symbolp form &optional form) body)))
  ;; It would be cleaner to create an uninterned symbol,
  ;; but that uses a lot more space when many functions in many files
  ;; use dolist.
  ;; FIXME: This cost disappears in byte-compiled lexical-binding files.
  (let ((temp '--dolist-tail--))
    ;; This is not a reliable test, but it does not matter because both
    ;; semantics are acceptable, tho one is slightly faster with dynamic
    ;; scoping and the other is slightly faster (and has cleaner semantics)
    ;; with lexical scoping.
    (if lexical-binding
        `(let ((,temp ,(nth 1 spec)))
           (while ,temp
             (let ((,(car spec) (car ,temp)))
               ,@body
               (setq ,temp (cdr ,temp))))
           ,@(if (cdr (cdr spec))
                 ;; FIXME: This let often leads to "unused var" warnings.
                 `((let ((,(car spec) nil)) ,@(cdr (cdr spec))))))
      `(let ((,temp ,(nth 1 spec))
             ,(car spec))
         (while ,temp
           (setq ,(car spec) (car ,temp))
           ,@body
           (setq ,temp (cdr ,temp)))
         ,@(if (cdr (cdr spec))
               `((setq ,(car spec) nil) ,@(cdr (cdr spec))))))))

(defmacro dotimes (spec &rest body)
  "Loop a certain number of times.
Evaluate BODY with VAR bound to successive integers running from 0,
inclusive, to COUNT, exclusive.  Then evaluate RESULT to get
the return value (nil if RESULT is omitted).

\(fn (VAR COUNT [RESULT]) BODY...)"
  (declare (indent 1) (debug dolist))
  ;; It would be cleaner to create an uninterned symbol,
  ;; but that uses a lot more space when many functions in many files
  ;; use dotimes.
  ;; FIXME: This cost disappears in byte-compiled lexical-binding files.
  (let ((temp '--dotimes-limit--)
        (start 0)
        (end (nth 1 spec)))
    ;; This is not a reliable test, but it does not matter because both
    ;; semantics are acceptable, tho one is slightly faster with dynamic
    ;; scoping and the other has cleaner semantics.
    (if lexical-binding
        (let ((counter '--dotimes-counter--))
          `(let ((,temp ,end)
                 (,counter ,start))
             (while (< ,counter ,temp)
               (let ((,(car spec) ,counter))
                 ,@body)
               (setq ,counter (1+ ,counter)))
             ,@(if (cddr spec)
                   ;; FIXME: This let often leads to "unused var" warnings.
                   `((let ((,(car spec) ,counter)) ,@(cddr spec))))))
      `(let ((,temp ,end)
             (,(car spec) ,start))
         (while (< ,(car spec) ,temp)
           ,@body
           (setq ,(car spec) (1+ ,(car spec))))
         ,@(cdr (cdr spec))))))

can a sane person read that? Lisp Macro is a after-thought, it is one of the worst hack in comp lang history, yet, got a cult status among lispers. (idiosyncratic quality often becomes a sacred statue of a group as identity, that will arose anger whenever placed in a negative light, ⁖ emacs's “*scratch*” buffer, Python's indentation, Mac's one-button mouse, lisp's parens.)

The proper solution is pattern matching, as in Mathematica.

the essence that makes lisp macro possible is: ① holding evaluation. ② symbols. You'll need symbols to do it effectively. Symbols allow the lang to distinguish evaluated result and unevaluated form. When this is done systematically for the whole lang, you have a term rewriting system, like Mathematica.

Of code in Mathematica, just about every line is pattern matching, most function definition is defining pattern replacement, as in lisp macros. But it is designed into the lang, so the syntax is readable, and far more powerful, and fast. If literal string find/replace is to regex, than lisp macro is to pattern matching. LOL

Is Your Language WEAK that You NEED Meta Programing?

the third issue of lisp macro, is that it is meta programing. As such, it's analogous to 1st order logic and higher order logic. If you have to use meta-programing to solve a problem, it indicates perhaps your language isn't expressive enough in the first place, or you are going round-about.

thank you. I'll stick with the emacs lisp primitive while.

blog comments powered by Disqus