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

backquote



    Date: Thu, 10 Nov 88 11:55:40 PST
    From: Don Cohen <donc@vaxa.isi.edu>

    I don't understand the 3 line explanation of backquotes in backquotes
    on p. 350 of CLtL.  I'd appreciate some enlightenment.  Also, I'd be
    interested in whether backquote is capable of solving the problem below.
    I surely don't see how to do it, but since I don't really understand
    embedded backquotes I'm not sure it can't be done.

    The problem:
    Suppose we want to declare versions of existing functions that do type
    checking, e.g., we want a protected version of (+ x y) which, if x and y
    are both numbers returns their sum and otherwise returns nil.  This could
    be done by the following macro:
    (defmacro protected-+ (x y)
      `(let ((x ,x) (y ,y)) (and (numberp x) (numberp y) (+ x y))))
    so that
    (macroexpand '(protected-+ a b))
    =>
    (LET ((X A) (Y B))
	 (AND (NUMBERP X) (NUMBERP Y) (+ X Y)))
    (The let assures that each argument is evaluated only once.)

    Since there are many such functions to declare, we might want a macro
    to define them, e.g., 
    (define-protected + (numberp x) (numberp y))
    would be a reasonable way to create the macro above.

    If we try to define this macro in the straight forward way we start
    out like so:

    (defmacro define-protected (function &rest args)
      `(defmacro ,(MAKE-PROTECTED-NAME function)
		 ,(mapcar #'cadr args)
	 `(let ,(BUILD-LET-LIST args)
	    (and ,@args
		 ,(BUILD-FUNCTION-CALL function args)))))

    Unfortunately, it appears that BUILD-LET-LIST has to build an expression 
    with more commas than backquotes, which the backquote readmacro cannot do.
    Another way to look at it is that I don't see how one backquote can 
    produce different backquoted expressions with varying numbers of commas.

The problem is that you are insisting on using the same argument names
in both places.  I think it becomes possible if you drop that
requirement, and just use GENSYMs.  However, I'm not going to try to
work it out, because I have a better idea:

It would be easier if protected-+ were defined to be a function, and I
don't see any reason why it shouldn't be (for efficiency,
DEFINE-PROTECTED could also include an INLINE proclamation).

(defmacro define-protected (function &rest arg-specs)
  (let ((protected-name (make-protected-name function))
	(args (mapcar #'cadr arg-specs)))
    `(progn
       (proclaim '(inline ,protected-name))
       (defun ,protected-name ,args
	 (and ,@arg-specs
	      (,function .,args))))))

Here's another variant.  This has the improvement (in my opinion) that
you don't build in assumptions about the format of the predicate call
(the MAPCAR #'CADR means that an argument can't be described as (and
(numberp x) (plusp x)).

(defmacro define-protected (function &rest arg-specs)
  "(define-protected <function> &rest (<arg1> <type1>) ...)"
  (let ((protected-name (make-protected-name function))
	(args (mapcar #'car arg-specs)))
    `(progn
       (proclaim '(inline ,protected-name))
       (defun ,protected-name ,args
	 ,@(mapcar #'(lambda (arg-spec)
		       `(check-type ,.arg-spec))
		   arg-specs)
	 (,function .,args)))))

This allows you to use arbitrary type specifiers (which are completely
general because of the (SATISFIES predicate) type spec).  In this case,
your example above becomes:

(define-protected + (x number) (y number))

                                                barmar