Обратная кавычка / обратная кавычка Common Lisp: как использовать?

У меня проблемы с макросом чтения обратных кавычек Lisp. Всякий раз, когда я пытаюсь написать макрос, который, кажется, требует использования встроенных обратных кавычек (например, ``(w ,x ,,y) из ANSI Common Lisp Пола Грэма, стр. 399), я не могу понять, как написать свой код таким образом что компилируется. Обычно мой код получает целую цепочку ошибок, которой предшествует фраза «Запятая не внутри обратной кавычки». Может ли кто-нибудь дать некоторые рекомендации о том, как я могу написать код, который будет правильно оценивать?

В качестве примера мне сейчас нужен макрос, который принимает форму, описывающую правило в форме '(function-name column-index value) и генерирующую лямбда-тело предиката, чтобы определить, удовлетворяет ли элемент, проиндексированный column-index для конкретной строки, правилу. Если бы я вызвал этот макрос с правилом '(< 1 2), я бы хотел, чтобы тело лямбда выглядело следующим образом:

(lambda (row)
  (< (svref row 1) 2))

Лучший удар, который я могу нанести по этому поводу, следующий:

(defmacro row-satisfies-rule (rule)
  (let ((x (gensym)))
    `(let ((,x ,rule))
       (lambda (row)
         (`,(car ,x) (svref row `,(cadr ,x)) `,(caddr ,x))))))

После оценки SBCL выдает следующий отчет об ошибке:

; in: ROW-SATISFIES-RULE '(< 1 2)
;     ((CAR #:G1121) (SVREF ROW (CADR #:G1121)) (CADDR #:G1121))
; 
; caught ERROR:
;   illegal function call

;     (LAMBDA (ROW) ((CAR #:G1121) (SVREF ROW (CADR #:G1121)) (CADDR #:G1121)))
; ==>
;   #'(LAMBDA (ROW) ((CAR #:G1121) (SVREF ROW (CADR #:G1121)) (CADDR #:G1121)))
; 
; caught STYLE-WARNING:
;   The variable ROW is defined but never used.

;     (LET ((#:G1121 '(< 1 2)))
;       (LAMBDA (ROW) ((CAR #:G1121) (SVREF ROW (CADR #:G1121)) (CADDR #:G1121))))
; 
; caught STYLE-WARNING:
;   The variable #:G1121 is defined but never used.
; 
; compilation unit finished
;   caught 1 ERROR condition
;   caught 2 STYLE-WARNING conditions
#<FUNCTION (LAMBDA (ROW)) {2497F245}>

Как я могу написать макросы для генерации нужного мне кода и, в частности, как реализовать row-satisfies-rule?


Используя идеи Ivijay и schemeulus, я модифицировал макрос так, чтобы он компилировался и работал, даже позволяя передавать формы в качестве аргументов. Он работает немного иначе, чем мой изначально запланированный макрос, поскольку я определил, что включение row в качестве аргумента для более плавного кода. Однако это уродливо, как грех. Кто-нибудь знает, как его очистить, чтобы он работал так же без вызова eval?

(defmacro row-satisfies-rule-p (row rule)
  (let ((x (gensym))
        (y (gensym)))
    `(let ((,x ,row)
           (,y ,rule))
       (destructuring-bind (a b c) ,y
         (eval `(,a (svref ,,x ,b) ,c))))))

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


person sadakatsu    schedule 24.06.2011    source источник
comment
Действительно ли gensyms необходимы? Вы не вводите никаких новых переменных в расширение, и имена переменных, которые вы используете для генерации расширения, не вызовут утечек.   -  person smackcrane    schedule 25.06.2011
comment
Уберите gensyms, уберите eval, а затем это три линии, выглядит красиво и работает правильно. Вы даже можете убрать destructuring-bind и сделать его двумя строчками.   -  person smackcrane    schedule 25.06.2011
comment
Стоит отметить, что макрос даже не нужен для этого; на самом деле функция может быть более подходящей.   -  person smackcrane    schedule 25.06.2011


Ответы (3)


Прежде всего, у макросов Lisp есть «деструктурирующие» списки аргументов. Это удобная функция, которая означает, что вместо того, чтобы иметь список аргументов (rule) и затем разбирать его с помощью (car rule) (cadr rule) (caddr rule), вы можете просто составить список аргументов ((function-name column-index value)). Таким образом, макрос ожидает список из трех элементов в качестве аргумента, и каждый элемент списка затем привязывается к соответствующему символу в списке аргументов. Вы можете использовать это или нет, но обычно так удобнее.

Далее, `, на самом деле ничего не делает, потому что обратная кавычка говорит Lisp не оценивать следующее выражение, а запятая говорит ему, чтобы он все-таки оценил его. Я думаю, вы имели в виду просто ,(car x), который оценивает (car x). В любом случае это не проблема, если вы используете аргументы деструктуризации.

И поскольку вы не вводите какие-либо новые переменные в расширение макроса, я не думаю, что (gensym) необходим в этом случае.

Итак, мы можем переписать макрос следующим образом:

(defmacro row-satisfies-rule ((function-name column-index value))
  `(lambda (row)
     (,function-name (svref row ,column-index) ,value)))

Что расширяется именно так, как вы хотели:

(macroexpand-1 '(row-satisfies-rule (< 1 2)))
=> (LAMBDA (ROW) (< (SVREF ROW 1) 2))

Надеюсь это поможет!


Если вам нужно оценить аргумент для получения набора правил, то вот хороший способ сделать это:

(defmacro row-satisfies-rule (rule)
  (destructuring-bind (function-name column-index value) (eval rule)
    `(lambda (row)
       (,function-name (svref row ,column-index) ,value))))

Вот пример:

(let ((rules '((< 1 2) (> 3 4))))
  (macroexpand-1 '(row-satisfies-rule (car rules))))
=> (LAMBDA (ROW) (< (SVREF ROW 1) 2))

как раньше.


Если вы хотите включить row в макрос, и он сразу же даст вам ответ, вместо того, чтобы создавать функцию для этого, попробуйте следующее:

(defmacro row-satisfies-rule-p (row rule)
  (destructuring-bind (function-name column-index value) rule
    `(,function-name (svref ,row ,column-index) ,value)))

Или, если вам нужно оценить аргумент rule (например, передать '(< 1 2) или (car rules) вместо (< 1 2)), просто используйте (destructuring-bind (function-name column-index value) (eval rule)


На самом деле функция кажется более подходящей, чем макрос для того, что вы пытаетесь сделать. Просто

(defun row-satisfies-rule-p (row rule)
  (destructuring-bind (function-name column-index value) rule
    (funcall function-name (svref row column-index) value)))

работает так же, как макрос, и намного аккуратнее, без всякого беспорядка с обратными кавычками, о котором нужно беспокоиться.

В общем, использование макросов для задач, которые могут быть выполнены функциями, - плохой стиль Лиспа.

person smackcrane    schedule 25.06.2011
comment
Когда я пытаюсь вызвать этот макрос, когда мои правила находятся в списке, например (row-satisfies-rule (car rules)), я получаю сообщение об ошибке, говорящее, что (car rules) неправильно отображается на три переменные. Как это исправить? - person sadakatsu; 25.06.2011
comment
@gamecoder проблема в том, что макросы не оценивают свои аргументы, поэтому он пытается привязать function-name к car и column-index к rules (а для value нет ничего, отсюда и ошибка). В этом случае я не думаю, что вы можете использовать список аргументов деструктуризации, вам нужно будет использовать один аргумент и оценить его, чтобы получить набор правил. Я добавил к своему ответу хороший способ сделать это (по сути, это то же самое, что и Левиджай использовал в своем ответе). - person smackcrane; 25.06.2011
comment
@discuplus: Где я могу изучить хороший стиль Lisp? Я только начал около шести месяцев назад, и я все еще немного возился. Мой код довольно гротескный, и пример Lisp, который я видел, был прекрасен: / - person sadakatsu; 25.06.2011
comment
@gamecoder ну, я не могу утверждать, что полностью понимаю хороший стиль Lisp (пока), но все, что я знаю о Lisp, я узнал из Практический Common Lisp и здесь о переполнении стека. Мой лучший совет - продолжать писать код, и если он не работает или кажется странным, просите критики; именно то, что вы здесь делаете. Вы начнете понимать, как писать красивый (и функциональный) Лисп. - person smackcrane; 26.06.2011

Следует понимать, что функция обратных кавычек совершенно не связана с макросами. Его можно использовать для создания списка. Поскольку исходный код обычно состоит из списков, он может быть полезен в макросах.

CL-USER 4 > `((+ 1 2) ,(+ 2 3))
((+ 1 2) 5)

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

Заметьте также, что это строго особенность читателя Лиспа.

Вышесказанное в основном похоже на:

CL-USER 5 > (list '(+ 1 2) (+ 2 3))
((+ 1 2) 5)

Это создает новый список с первым выражением (не оценивается, потому что заключен в кавычки) и результатом второго выражения.

Почему в Лиспе используются обратные кавычки?

Потому что он предоставляет простой механизм шаблонов, когда кто-то хочет создать списки, в которых большинство элементов не оцениваются, но некоторые оцениваются. Кроме того, список с обратными кавычками похож на список результатов.

person Rainer Joswig    schedule 25.06.2011

вам не нужны вложенные обратные кавычки, чтобы решить эту проблему. Кроме того, когда это макрос, вам не нужно цитировать аргументы. Так что (row-satisfies-rule (< 1 2)) шепелявее, чем (row-satisfies-rule '(< 1 2)).

(defmacro row-satisfies-rule (rule)
  (destructuring-bind (function-name column-index value) rule
    `(lambda (row)
       (,function-name (svref row ,column-index) ,value))))

решит проблему для всех звонков в первой форме. Решение проблемы при втором виде оставлено как упражнение.

person lvijay    schedule 25.06.2011