Ansi Common Lisp Грэма: с.170 с трудностями в понимании примера - PullRequest
0 голосов
/ 21 ноября 2018
(defmacro random-choice (&rest exprs)
    `(case (random ,(length exprs))
        ,@(let ((key -1))
            (mapcar #'(lambda (expr)
                        `(,(incf key) ,expr))
                    exprs))))      

Итак, я выполнил macroexpand-1 для этой функции, и я в целом понимаю, как работает этот макрос, но я очень озадачен тем, как Грэм вложил в кавычку `и как он использует @ для расширения кейсов.

  • Когда мы можем вкладывать обратные кавычки?
  • Почему Грэхем вкладывает в этот пример обратные кавычки?
  • Почему ,@ расширяет дела в (random ,(length exprs)) дела?
  • Я понимаю, что mapcar в первую очередь для того, чтобы мы могли увеличить key, но как этот макрос узнает, что он применяет mapcar всего (random ,(length exprs)) раз?
  • Как формируется неявный список, в котором сплайсируется запятая в ,@?

Примечание. Я очень глуп, поэтому, пожалуйста, объясните в самых основных терминах.

РЕДАКТИРОВАТЬ:

Теперь я понимаю, что самая внутренняя обратная кавычка (,(incf key) ,expr) гарантирует, что эта функция сначала вычисляется, поэтому она приблизительно эквивалентна (list (incf key) expr), затем

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

оценивается как что-то вроде списка '((0 a_0) (1 a_1) ... (n a_n)), и так как у нас есть ,@, то это 'объединяется' в

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

Наконец (case (random ,(length exprs)) оценивается в case (random n), и это дает нама также внешние скобки, оставляя нам

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

Правильно ли я понял последовательность событий?Я не смог найти в Интернете никаких ресурсов для проверки, и книга Грэхема так не ломает.

Ответы [ 2 ]

0 голосов
/ 21 ноября 2018

Другой способ написать это (избавиться от LET, MAPCAR + код побочного эффекта INCF):

CL-USER 44 > (defmacro random-choice (&rest exprs &aux (n (length exprs)))
               `(case (random ,n)
                  ,@(loop for ci below n and expr in exprs
                          collect `(,ci ,expr))))
RANDOM-CHOICE

CL-USER 45 > (macroexpand-1 '(random-choice 10 21 32 43))

(CASE (RANDOM 4) (0 10) (1 21) (2 32) (3 43))

Макрос использует форму обратной кавычки с вычислениями внутри.Мы можем извлечь вычисление и присвоить части переменным:

CL-USER 46 > (defmacro random-choice (&rest exprs &aux (n (length exprs)))
               (let ((keyform `(random ,n))
                     (clauses (loop for ci below n and expr in exprs
                                    collect `(,ci ,expr))))
                 `(case ,keyform
                    ,@clauses)))
RANDOM-CHOICE

CL-USER 47 > (macroexpand-1 '(random-choice 10 21 32 43))

(CASE (RANDOM 4) (0 10) (1 21) (2 32) (3 43))

Как вы можете видеть, формы обратных цитат могут быть вычислены независимо и собраны в форме обратной цитаты в конце.

КогдаФункция макроса содержит фрагменты кода, предпочтительно сохранять их в виде кавычек или обратных кавычек -> это облегчает их идентификацию в функции макроса.Замена их вычислением по списку (с использованием list, cons, ...) менее удобна и менее читабельна.Но тогда нужно получить правильную последовательность обратной / обратной цитаты.В моем примере это немного проще, потому что части вычисляются независимо.Это помогает понять макрос, так как он немного больше соответствует синтаксису case:

CASE keyform {normal-clause}* [otherwise-clause]

normal-clause::= (keys form*) 

Здесь мы используем только предложения keyform и 0..n-1 из {normal-clause}*,Мы также не используем otherwise-clause.

0 голосов
/ 21 ноября 2018

Когда мы можем вкладывать обратные кавычки?

Вы всегда можете вкладывать обратные кавычки.Однако обратите внимание, что этот код не вкладывает их:

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

Вложенные обратные кавычки выглядят следующим образом:

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

Почему Грэм вкладывает обратные кавычки в этом примере?

Он их не вкладывает.Он генерирует внешнее тело кейса с первыми обратными кавычками, а затем заполняет его кейсами, которые генерируются с помощью mapcar.Чтобы написать код, который будет сгенерирован для каждого случая, он использует вторую обратную цитату

Почему ,@ расширяет случаи в (random ,(length exprs)) случаях?

Itне делает этогоЭто распространяется на (length exprs) случаев.Строго говоря, он сливается со списком вещей, возвращаемых тем, что находится внутри него, в данном случае это список выражений.

Я понимаю, что mapcar предназначен в первую очередь для того, чтобы мы могли увеличить ключ, но как этот макрос узнает о применении mapcar всего (random ,(length exprs)) раз?

Это не то, что делает mapcar или для чего оно.

Как формируется неявный список, который запятая @ создает из сплайсинга?

Это то, что делает mapcar.


Toобратитесь к редактированию, вы получили некоторые вещи наполовину правильно, но у вас слишком много паренов.

mapcar применяет функцию к каждому элементу списка по порядку, собирая результаты в список:

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)))

Это будет входить в тело выражения case: если случайное значение равно 0, вернуть FOO, если оно равно 1, то BAR и т. Д.

Toполучите это случайное значение, которое вы делаете (random 3) для случайного целого числа от 0 до 2 включительно.

...