Paul Graham's condlet without gensym for variable names

Discussion of Common Lisp
Post Reply
vaclav
Posts: 2
Joined: Mon Feb 21, 2011 3:54 am

Paul Graham's condlet without gensym for variable names

Post by vaclav » Mon Feb 21, 2011 4:26 am

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

Warren Wilkinson
Posts: 117
Joined: Tue Aug 10, 2010 11:24 pm
Location: Calgary, Alberta
Contact:

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

Post by Warren Wilkinson » Mon Feb 21, 2011 11:40 am

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)
Need an online wiki database? My Lisp startup http://www.formlis.com combines a wiki with forms and reports.

vaclav
Posts: 2
Joined: Mon Feb 21, 2011 3:54 am

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

Post by vaclav » Tue Feb 22, 2011 5:24 am

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

Post Reply