Graham's Ansi Common Lisp: p.170 having trouble understanding example

  • A+
Category:Languages
(defmacro random-choice (&rest exprs)     `(case (random ,(length exprs))         ,@(let ((key -1))             (mapcar #'(lambda (expr)                         `(,(incf key) ,expr))                     exprs))))       

So I performed macroexpand-1 on this function and I understand generally how this macro works, but I'm super confused of how Graham nest the backquote `, and how he uses ,@ to expand the cases.

  • When can we nest backquotes?
  • Why does Graham nest the backquotes in this example?
  • Why does ,@ expand the the cases into the (random ,(length exprs)) cases?
  • I understand that the mapcar is primarily so that we can increase the key, but how does this macro know to apply mapcar a total of (random ,(length exprs)) times?
  • How is the implicit list that comma-at ,@ is splicing being formed?

Note I am very stupid so please explain in the most basic of terms.

EDIT:

I now understand that the inner most backquote (,(incf key) ,expr) ensures that this function is evaluated first so its roughly equivalent to (list (incf key) expr), then

,@(let ((i 0))     (mapcar #'(lambda (expr)         `(,(incf i) ,expr))         args)) 

is evaluated into something like the list '((0 a_0) (1 a_1) ... (n a_n)), and since we have ,@ then this is 'spliced' into

((0 a_0)) ((1 a_n))     .     .     . ((n a_n)) 

Lastly (case (random ,(length exprs)) is evaluated to case (random n) and it gives us the outer parenthesis as well, leaving us with

(case (random n)     ((0 a_0))     ((1 a_n))         .         .         .     ((n a_n))) 

Did I get the sequence of events correct? I couldn't find any resources online to verify and Graham's book does not break it down like this.

 


When can we nest backquotes?

You can always nest backquotes. However note that this code does not nest them:

`(foo ; in 1 backquote here    ,@(cons 'bar ; in 0 backquotes here            `(baz ; in 1 backquotes here               ,(random 3)))) 

Nesting backquotes looks like:

`(let ((x `(,,y ,z))) ...) 

Why does Graham nest the backquotes in this example?

He does not nest them. He generates the outer body of the case with the first backquotes, then fills it with the cases which are generated with mapcar. To write the code to be generated for each case he uses a second backquote

Why does ,@ expand the the cases into the (random ,(length exprs)) cases?

It does not do that. It expands into (length exprs) cases. Strictly it merges in the list of things returned by whatever is inside it, in this case a list of expressions.

I understand that the mapcar is primarily so that we can increase the key, but how does this macro know to apply mapcar a total of (random ,(length exprs)) times?

That is not what the mapcar does or what it is for.

How is the implicit list that comma-at ,@ is splicing being formed?

This is what mapcar does.


To address your edit, you got some things half correct but you have too many parens.

mapcar applies a function to each element of a list in order, collecting the results into a list:

CL-USER> (mapcar #'1+ '(1 2 3)) (2 3 4) CL-USER>(let ((key -1))           (mapcar (lambda (x)                     `(,incf key) ,x))                   '(foo bar (baz wazoo))) ((0 FOO) (1 BAR) (2 (BAZ WAZOO))) 

This is going go into the body of the case expression: If the random value is 0 then return FOO, if it is 1 then BAR, and so on.

To get this random value you do (random 3) for a random integer between 0 and 2 inclusive.

Comment

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen: