Common Lisp Backquote / Backtick: как использовать? - PullRequest
9 голосов
/ 25 июня 2011

У меня проблемы с макросом чтения обратной цитаты Лиспа.Всякий раз, когда я пытаюсь написать макрос, который требует использования встроенных обратных кавычек (например, ``(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?


Используя идеи Ивиджея и ученика, я изменил макрос так, чтобы он компилировался и работал, даже позволяя формам передаваться в качестве аргументов.Он немного отличается от моего первоначально запланированного макроса, так как я определил, что включение 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))))))

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

Ответы [ 3 ]

12 голосов
/ 25 июня 2011

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

Далее `, на самом деле ничего не делает, потому что обратная кавычка говорит Лиспу не оценивать следующее выражение, а запятая говорит ему оценивать егов конце концов.Я думаю, что вы имели в виду просто ,(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)))

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

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

5 голосов
/ 25 июня 2011

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

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

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

Обратите также внимание, что это является особенностью считывателя Lisp.

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

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

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

Почему Lisp предоставляет нотацию обратной цитаты?

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

4 голосов
/ 25 июня 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))))

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

...