[Top][All Lists]

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

CL package serious deficiencies

From: egnarts-ms
Subject: CL package serious deficiencies
Date: Mon, 6 Feb 2012 06:18:37 -0800 (PST)

Hello to everyone ! In this message I would like to share my (mostly
negative) experience with CL package of Elisp.  I'm sure, some Emacs/Elisp
gurus are already familiar with many of these, but I still believe I have to
say something new.  Let's start.

1. The most striking thing is the way symbol macros are implemented: authors
of CL merely put cons cells ("symbol-name" <expansion>) onto
`cl-macro-environment' variable, and when the time of possible
macroexpansion comes, this alist is searched with assq -- that it, strings
are compared with `eq'.
  This obviously is not correct. Look at this example:

(defmacro test-macro ()
  (let* ((symbol-1 'sym)
         (symbol-2 (make-symbol (symbol-name symbol-1))))
    `(let ((v1 0)
           (v2 0))
       (symbol-macrolet ((,symbol-1 (incf v1))
                                  (,symbol-2 (incf v2)))
         (list v1 v2)))))

(princ (test-macro))

This code prints (0 2), when it should print (1 1): that's because the same
string object serves the purpose of being the symbol name for 2 distinct
symbols. This situation is only possible if we deal with uninterned symbols
(and in more-or-less serious Lisp programming this practice is quite

I tested this exampe (as well as all the following ones) in CLISP 2.41.1; it
prints (1 1).

2. Generalized variables.

`get-setf-method' works by calling `macroexpand' when it cannot find a
setf-method for a form.  `macroexpand' honestly expands this form the whole
way down, without regarding to all the intermediate forms; 
http://www.lispworks.com/documentation/HyperSpec/Body/05_abg.htm Common Lisp
uses  `macroexpand-1' for the same purpose.  This means that in case of any
intermediate macroexpansion result having a setf-method defined, it will be
ignored.  Only the end non-macro-call form is considered for setf-method.

I cannot give right away a good example of a harm this may cause. 
Nevertheless, this behavior seems fishy.. for instance, defining
setf-methods for macro names may lead to counterintuitive things: imagine
there is some other macro named B that expands into the call of this macro
(named, say, A), and A expands into a non-macro form X (for instance, just
car of smth).  Given that B has no setf-method defined, when
`get-setf-method' searches for the setf-method for B, the setf-method for A
doesn't count at all: it is just skipped.  X is what is searched in this

`macroexpand-1' is absent in Emacs Lisp.

3. The way `get-setf-method', `incf', `decf', `push', `pushnew' and `pop'
deal with symbol macros.
Look at this:
(defun get-setf-method (place &optional env)
  (if (symbolp place)
      (let ((temp (make-symbol "--cl-setf--")))
        (list nil nil (list temp) (list 'setq place temp) place))

`get-setf-method' is absolutely ignorant of symbol macros: the fact that
"place" satisfies "symbolp" doesn't in any way mean it is a simple variable,
since we have a concept of symbol macro in the language.  The same thing is
true about all 5 functions listed above. 

To see a real example, consider the following:

 '(symbol-macrolet ((s (cadr (get-some-list))))
    (incf s)))

which results in

(let* ((--cl-x-- (get-some-list)))
  (setcar (cdr --cl-x--)
             (1+ (cadr (get-some-list)))))

this calls "get-some-list" 2 times, which is apparently erroneous.  Again,
Common Lisp makes only 1 call.

4. `cl-macroexpand-all'. This should be blamed for 2 things.

First, it doesn't macroexpand all, in contrast to what its name states.  Try
out this:

 '(let (((car x) 10))

You get the following result:

(letf (((car x) 10)) (body-1)),

and this is not the result of the macroexpansion ! Yes, I agree that this
case is unlikely: `let' is not supposed to bind places, this feature was
supposed to be internal to CL implementation.. but wait, this function is
nevertheless cheating.  It doesn't do what it must; it reckons upon someone
else to finish the job.  In some advanced, sophisticated code this hidden
flaw may suddenly show itself.

Second, look at the following code:

((eq (car form) 'setq)
 (let* ((args (cl-macroexpand-body (cdr form) env)) (p args))
   (while (and p (symbolp (car p))) (setq p (cddr p)))
   (if p (cl-macroexpand-all (cons 'setf args)) (cons 'setq args))))

This is actually a cond clause from `cl-macroexpand-all'.  See, it looks
whether any of "setq" targets in not a simple symbol, and if it's not, we
end up with a "setf" form being processed instead of original "setq" form. 
We have already discussed that problem: full expansion of "setq" arguments. 
No intermediate setf-methods have a chance to come up and be considered.

5. Loop facility, if/when/unless clause.  This is the most violent point of
all above.

To put it short, in the loop clause that follows "if" we can access the
result of evaluation of the condition with the name "it".  But have a look
at how they have implemented that "it":

(setq form (list* 'if (list 'setq temp cond)
                       (subst temp 'it form)))

Just subst, and that's all.  There's no surprise that when I put my
anaphoric "awhen" construct (inspired by Paul Graham) inside the clause
following "when" in a loop, I got something uncompilable.

I can draw the only conclusion from all above: Elisp has not been used much
to create true Lispy programs.  Features like symbol macros and generalized
variables were introduced to be quite close to those respective concepts of
Common Lisp, but they are still not that mature.

I've managed to fix all above (except `pop', `push', `incf'.. -- they proved
hard to be changed and recompiled correctly), and I'm still using Emacs, and
I'm still programming in Elisp.  I'm sure Elisp does his job of being an
excellent training ground for learning Lisp concepts.
View this message in context: 
Sent from the Emacs - Dev mailing list archive at Nabble.com.

reply via email to

[Prev in Thread] Current Thread [Next in Thread]