Page 1 of 1

Paul Graham's condlet without gensym for variable names

Posted: Mon Feb 21, 2011 4:26 am
by vaclav
Hi,
I'm relatively new to Lisp, but relatively old to computers. I'm reading through Paul Graham's On Lisp book, and I would like to ask anybody who is familiar with or can understand Paul Graham's condlet macro about his usage of gensyms there. From my point of view there's no need to use a new "gensymed" set of variables for the bindings as the variables are locally rebound by the lets the macro generates for each conditional binding. I modified the original code of condlet so that it rebinds the original variables and it looks to work without any problem. I must have overlooked something...

For those who are not familiar with the On Lisp book and condlet macro, here's an example (the book is available online for free at http://www.paulgraham.com/onlisp.html):
The condlet macro allows to evaluate a common piece of code with conditional binding.

Code: Select all

(let ((x 2))
(condlet (((= x 1)(x 'a)(y 'b))
          ((=x 2)(x 'c)))
  (list x y))
returns (c nil)

Paul Graham's code for condlet:

Code: Select all

(defmacro condlet (clauses &body body)
  (let ((body-fn (gensym))
        (vars (mapcar #'(lambda (var) (cons var (gensym)))
                (remove-duplicates 
                 (mapcar #'car 
                   (mappend #'cdr clauses))))))
    `(labels ((,body-fn ,(mapcar #'car vars)
                        ,@body))
       (cond ,@(mapcar #'(lambda (clause)
                           (condlet-clause clause vars body-fn))
                 clauses)))))


(defun condlet-clause (clause vars body-fn)
  `(,(car clause) (let ,(mapcar #'cdr vars)
                    (let ,(condlet-binds clause vars)
                      (,body-fn ,@(mapcar #'cdr vars))))))
  

(defun condlet-binds (clause vars)
  (mapcar #'(lambda (bindform)
              (if (consp bindform)
                  (cons (cdr (assoc (car bindform) vars))
                        (cdr bindform))))
    (cdr clause)))
The modified code without gensym:

Code: Select all

(defmacro condlet (clauses &body body)
  (let ((body-fn (gensym))
        (vars (remove-duplicates 
                 (mapcar #'car 
                   (mappend #'cdr clauses)))))
    `(labels ((,body-fn ,vars
                        ,@body))
       (cond ,@(mapcar #'(lambda (clause)
                           (condlet-clause clause vars body-fn))
                 clauses)))))


(defun condlet-clause (clause vars body-fn)
  `(,(car clause) (let ,vars
                    (let ,(cdr clause)
                      (,body-fn ,@vars)))))
Any help appreciated.
Vaclav

Re: Paul Graham's condlet without gensym for variable names

Posted: Mon Feb 21, 2011 11:40 am
by Warren Wilkinson
It might be because in condlet-clause all the variables are bound to nil before the clause runs and computes what they should be. With gensyms, the gensyms are bound to nil. Without gensyms, the variables (every variable mentioned in condlet) will get bound to nil.


Code: Select all

(defun test (a)
  (condlet ((t  (b 99))
                 (nil (a 'wrong)))
      (list a b)))
Here is a testable prediction:
With gensyms (test 'alpha) ==> '(alpha 99)
Without gensyms (test 'alpha) ==> '(nil 99)

Re: Paul Graham's condlet without gensym for variable names

Posted: Tue Feb 22, 2011 5:24 am
by vaclav
Actually, both versions give the same result: (nil 99).
Note that this is the expected behaviour (..."Variables which occur in some clauses and not others will be bound to nil if the successful clause does not specify bindings for them"...)

Paul Graham uses gensyms for the bindings instead of the original variable names (a, b in your example) but regardless of what binding is selected the body of the condlet construct (list a b) gets wrapped in a lambda(a b) function which gets called with the according gensyms as parameter values. So there's no chance for the original binding of 'a' being used inside of the lambda function.

Meanwhile I dug a little bit in the condlet macro and found out where the problem is. It's the expressions that evaluate to the new value of the variables that makes the difference. Consider this code:

Code: Select all

(let ((x 2))
  (condlet (((= x 1)(x (1+ x))(y 'b))
          ((= x 2)(x (1+ x))))
                     (list x y)))
with gensyms returns '(3 nil)
without gensyms gives an error: nil is not of the expected type 'number'

As the condlet macro is written in such a way that the generated code binds ALL variables to nil at the beginning of each cond branch and then rebinds only those used in this branch to new values, the original values get lost if gensyms are not used.

Thank you for your answer which started me to think about it once more.

Vaclav