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

commonlisp types



    Date: Fri, 2 Dec 88 11:33:22 EST
    From: Guy Steele <gls@Think.COM>

       Date: Fri, 2 Dec 1988 00:40-EST 
       From: Jamie.Zawinski <jwz@spice.cs.cmu.edu>
       ...
       Can someone explain the rationale behind forcing SATISFIES to 
       accept only function-names and not lambda expressions?
       I can see that the compiler could have special knowledge about
       such forms as (SATISFIES PLUSP), but CLtL says lambdas are excluded
       "to avoid scoping problems."

    Consider

    (defun bazola (linguini pop-tarts)
      (declare (type (satisfies (lambda (x) (< x linguini))) pop-tarts))
      ...)

    I'm trying to say that pop-tarts is always smaller in value than linguini.
    The lambda expression appears lexically within the binding of linguini,
    so one might expect that the free reference to linguini is legitimate.
    But it can't work.

    Similarly this cannot work:

    (defun bazola (linguini pop-tarts)
      (assert (typep pop-tarts '(satisfies (lambda (x) (< x linguini)))))
      ...)

    [Of course, this can be rendered instead as

    (defun bazola (linguini pop-tarts)
      (assert (< pop-tarts linguini))
      ...)

    but that is beside the point.]

    One might conceivably argue that SATISFIES should allow an actual
    function and not just a name; then one might try

    (defun bazola (linguini pop-tarts)
      (assert (typep pop-tarts `(satisfies ,(lambda (x) (< x linguini)))))
      ...)

    but this approach doesn't help the declaration case.  It's a basic problem
    of compile-time versus run-time execution.

    --Guy

Fyi, it turns out this rationale doesn't hold as much water as you'd think.
Consider:

 (defun bar (x) (symbolp x))

 (defun foo (x)
   (flet ((bar (y) (integerp y)))
     (typep x '(satisfies bar))))

 (foo 'x)

The correct answer is T, but I bet a lot of implementations return NIL
in compiled code.

Anyway, my main point is that the reason for prohibiting lambda expressions
isn't that they're not meaningful, only that they're hard to reason about.
But since an analogous argument can be made for symbols, the rationale breaks
down.

Since (SATISFIES BAR) means that (FUNCALL (SYMBOL-FUNCTION 'BAR) ...) is
true, not that (FUNCALL #'BAR ...) is true, then it follows that
(SATISFIES (LAMBDA (X) (AND (BAR X) (BAZ X)))) means
(FUNCALL (EVAL '#'(LAMBDA (X) (AND (BAR X) (BAZ X)))) ...) is true, not
that
(FUNCALL #'(LAMBDA (X) (AND (BAR X) (BAZ X))) ...) is true.

The real truth is that we thought the scoping problems were limited to
LAMBDA expressions because we weren't used to reasoning about FLET, which
was a new construct at the time we designed CL. If we had it to do over,
I'd certainly be lobbying strongly for permitting lambda expressions.

The Common-Lisp mailing list doesn't have the authority to change the 
language, so I'll save any proposals to change things for other forums.
But I did want to publicly debunk the myth behind this design decision.

Btw, if LAMBDA expressions -were- permitted, the BAZOLA example you suggest
could, in addition to the more obvious way you cite, be written as:

    (defun bazola (linguini pop-tarts)
      (declare (special linguini))
      (assert (typep pop-tarts '(satisfies (lambda (x)
					     (declare (special linguini))
					     (< x linguini)))))
      ...)

This works even if you open code it in the obvious way, though it has
the disadvantage that figuring out that the SPECIAL declaration was needed
only for the sake of the transition into the SATISFIES and not for some
function called within the opened LAMBDA may be tricky. So the open-coded
form may do a needless special bind in complex cases involving calls to
user-defined predicates.