0
votes

I decided I would tidy up the following function. The idea being that it uses cond but it also contains ifs, which makes it hard to cognitively process. This is bst code from the book ansi common lisp.

(defun percolate (bst)                      
  (cond ((null (node-l bst))                
         (if (null (node-r bst))
             nil
             (rperc bst)))
        ((null (node-r bst)) (lperc bst))
        (t (if (zerop (random 2))
               (lperc bst)
               (rperc bst)))))

My idea was to remove the ifs, by adding more cases in the cond, then justify the whole thing nicely with cause on the left, effect on the right. Here's what I came up with:

(defun percolate (bst)                      ; [6,7,7a]
  (cond (((and (null (node-l bst)) (null (node-r bst)))  nil)
         ((null (node-l bst))                            (rperc bst))
         ((null (node-r bst))                            (lperc bst))
         (t                                              (if (zerop (random 2))
                                                             (lperc bst)
                                                             (rperc bst))))))

However, this produces the error

*** - SYSTEM::%EXPAND-FORM: (AND (NULL (NODE-L BST)) (NULL (NODE-R BST))) should be a
      lambda expression

I can find other posts about this problem on stack overflow, like here but I still don't understand it. Is cond a departure from normal lisp syntax somehow? I'd like to identify the assumption I'm making which is wrong.

For the record, the below code is accepted by the interpreter, but obviously we don't want to write it like that.

(defun percolate (bst)                      ; [6,7,7a]
  (let ((both-null (and (null (node-l bst)) (null (node-r bst))))
        (l-null    (null (node-l bst)))
        (r-null    (null (node-r bst))))
  (cond ((both-null                                              nil)
         (l-null                                         (rperc bst))
         (r-null                                         (lperc bst))
         (t                                              (if (zerop (random 2))
                                                             (lperc bst)
                                                             (rperc bst)))))))
2
You may want to consider doing it differently: (random-elt (delete nil (vector left right)))coredump
I think the random bit in his percolate is wrong as explained in the book errata. Someone has commented that "A deleted internal node needs to be replaced either by the maximal node in the left subtree or by the minimal node in the right subtree, and your function does not do this" ... but I haven't fully understood that yet... but my post was just about syntax though... :)mwal

2 Answers

4
votes

cond syntax:

cond {clause}*

clause::= (test-form form*) 

examples

(cond (var)
      (var a b c)
      (var (f a) (f b) (f c))
      ((and var-a var-b) (f c) (f d))
      ((f a) b (f c) d)
      (t (f a) (f b) (f c)))

Means:

  • zero or more clauses.
  • each clause is inside a list and begins with a test form and then zero or more forms

let syntax:

let ({var | (var [init-form])}*)
  declaration*
  form*

Means:

  • zero or more variable clauses inside one list
  • each variable clause is either a variable or a list of variable and optional init-form
  • the variable clauses list is followed by optional zero or more declarations and then zero or more body forms

Example:

(let ()
   )

(let (a)
  a)

(let (a
      (b t))
  (and a b))

(let ((a t)
      (b (not t)))
  (or a b))
0
votes

I identified a possible answer during the course of writing the post.

I'll still make the post though as it would be great to receive any further clarification and discussion.

Lets first state what we know. With let, it is necessary to have a surrounding pair of parens to contain all of the individual lexical variables assigned:

(let ((both-null (and (null (node-l bst)) (null (node-r bst))))
      (l-null    (null (node-l bst)))
      (r-null    (null (node-r bst))))

But it turns out a mistake to assume cond follows the same pattern (which I had assumed).

Cond works just fine without surrounding parens. The following form is accepted by the interpreter:

(defun percolate (bst)                      ; [6,7,7a]
  (cond ((and (null (node-l bst)) (null (node-r bst)))  nil)
        ((null (node-l bst))                            (rperc bst))
        ((null (node-r bst))                            (lperc bst))
        (t                                              (if (zerop (random 2))
                                                            (lperc bst)
                                                            (rperc bst)))))

However (here's the part that bit me), it turns out the interpreter will accept extra parens, if one unwittingly copies the let form syntax.

... unless! - the first form in one of the cond expressions is itself is a compound form, i.e. not an atom.

I'd greatly appreciate any additional answers from the community which can explain further and/or extend or correct what I've found here.

UPDATE: my test of validity of these forms had been to paste them into the REPL. It turns out that is not sufficient, you must compile, as explained in the comments by Rainer Joswig.

So, some conclusions

  • Make sure to remember cond does not follow the syntactic model of let
  • Be careful because if you fall into that assumption, you can get away with it for a while then suddenly it wont work for the reason given above.
  • If you've been writing cond with extra parens, practice it correctly and learn to recognise it without.