Макрос, определяющий функции, имена которых основаны на аргументах макроса
*Примечание: несмотря на то, что я часто посещал StackOverflow в течение длительного времени, это первый вопрос, который я разместил сам. Прошу прощения, если это слишком многословно. Конструктивную критику оценили.
когда я определяю структуру в Common Lisp с помощью defstruct, функция предиката автоматически генерируется, которая проверяет, является ли ее аргумент типом, определенным defstruct. Например:
(defstruct book
title
author)
(let ((huck-finn (make-book :title "The Adventures of Huckleberry Finn" :author "Mark Twain")))
(book-p huck-finn))
=> True
однако при определении класса с помощью defclass такие функции по-видимому, не генерируется по умолчанию (есть ли способ указать это?), поэтому я пытаюсь добавить эту функциональность сам, потому что я хотел бы a) чтобы этот синтаксис был согласован между структурами и классами, b) иметь аббревиатуру (typep obj 'classname)
, который мне нужно писать очень часто и визуально шумно,
и в) как упражнение по программированию, поскольку я все еще относительно новичок в Lisp.
я мог бы написать макрос, который определяет функцию предиката с именем a класс:
(defclass book ()
((title :initarg :title
:accessor title)
(author :initarg :author
:accessor author)))
;This...
(defmacro gen-predicate (classname)
...)
;...should expand to this...
(defun book-p (obj)
(typep obj 'book))
;...when called like this:
(gen-predicate 'book)
имя, которое мне нужно передать defun, должно иметь форму 'classname-p
. Вот где у меня проблемы. Чтобы создать такой символ, я мог бы использовать функцию "symb" от Пола Грэма на Lisp (стр. 58). Когда он запускается на REPL:
(symb 'book '-p)
=> BOOK-P
мой макрос gen-predicate выглядит так:
(defmacro gen-predicate (classname)
`(defun ,(symb classname '-p) (obj)
(typep obj ,classname)))
(macroexpand `(gen-predicate 'book))
=>
(PROGN
(EVAL-WHEN (:COMPILE-TOPLEVEL) (SB-C:%COMPILER-DEFUN '|'BOOK-P| 'NIL T))
(SB-IMPL::%DEFUN '|'BOOK-P|
(SB-INT:NAMED-LAMBDA |'BOOK-P|
(OBJ)
(BLOCK |'BOOK-P| (TYPEP OBJ 'BOOK)))
NIL 'NIL (SB-C:SOURCE-LOCATION)))
T
казалось бы, что символ создается (symb 'book '-p)
на самом деле считается |'BOOK-P|
реализацией (SBCL), а не BOOK-P
. Конечно достаточно, теперь это работает:
(let ((huck-finn (make-instance 'book)))
(|'BOOK-P| huck-finn))
=> True
почему символ, созданный symb, интернирован как |'BOOK-P|
? В On Lisp (на той же странице, что и выше) Грэм говорит: "любая строка может быть печатным именем символа, даже строка, содержащая строчные буквы или макросимволы, такие как круглые скобки. Когда имя символа содержит такие странности, оно печатается в вертикальных полосах.- В данном случае таких странностей не существует, не так ли? И правильно ли я думаю, что "печатное имя" символа-это то, что на самом деле отображается на стандартном выходе, когда символ печатается, и в случае таких странностей отличается от формы самого символа?
чтобы иметь возможность писать макросы, определяющие функции, такие как gen-predicate
- чьи определенные функции названы на основе аргументов, переданных макро-кажется мне чем-то, что хакеры Lisp, вероятно, делали в течение веков. Пользователь Kaz говорит здесь (слияние символов в общем lisp), что" затирание " символов может часто его следует избегать, но это разрушит цель этого макроса.
наконец, предполагая, что я могу получить gen-predicate
чтобы работать так, как я хочу, каков был бы лучший способ гарантировать, что он будет вызываться для каждого нового класса, как они определены? Так же как initialize-instance
смогите быть подгоняно для того чтобы выполнить некоторые действия на экземпляров класса, есть ли общая функция, вызываемая defclass, которая может выполнять действия при определение в класс?
спасибо.
2 ответов
это обычная проблема: что передается макросу?
сравните такие вызовы:
(symb 'book '-p)
и
(symb ''book '-p)
ваша макроформа такова:
(gen-predicate 'book)
GEN-PREDICATE
макрос. classname
является параметром для этого макроса.
теперь, что такое значение classname
внутри макроса во время расширения кода? Это book
или 'book
?
на самом деле это последнее, потому что вы написали (gen-predicate 'book)
. Помните: макросы см. исходный код, и источник аргумента передается функции макросов-не значение. Аргумент 'book
. Таким образом это передается. (QUOTE BOOK)
то же самое, только печатается по-разному. Таким образом, это список из двух элементов. Первый элемент-символ QUOTE
и второй элемент является символом BOOK
.
таким образом, макрос теперь вызывает функцию SYMB
со значением аргумента (QUOTE BOOK)
или, короче, 'BOOK
.
если вы хотите сгенерировать предикат без символа кавычки, нужно написать:
(gen-predicate book)
вы также можете изменить макрос:
(symb classname '-p)
будет:
(symbol (if (and (consp classname)
(eq (first classname) 'quote))
(second classname)
classname))
сравнить
пишем
(defun foo () 'bar)
, а не
(defun 'foo () 'bar) ; note the quoted FOO
DEFUN
это макрос и первый аргумент-имя функции. Похожая проблема...
вторая часть вопрос
я действительно не знаю никакого хорошего ответа на это. Я не могу вспомнить простой способ запуска кода (например, для определения функции) после определения класса.
может быть, использовать швабру, но это уродливо.
написать пользовательский макрос
DEFINE-CLASS
который делает то, что вы хотите: расширяется вDEFCLASS
иDEFUN
.перебрать все символы в пакете, найти занятия и определить соответствующие предикаты
чтобы решить вторую часть вопроса, классы сами являются объектами, благодаря MOP, поэтому это может можно написать: after метод на initialize-экземпляр, специализирующийся на стандартном классе. Но вы должны проверить швабру, чтобы увидеть, разрешено ли определение такого метода или нет.
Если это возможно, то да, вы можете запустить код в ответ на создание класса; однако, поскольку вы не знаете имя создаваемого класса до выполнения, вы не удается записать его текстуально в источнике, поэтому вы не можете использовать свой макрос (если вы не используете eval). Вы бы предпочли использовать что-то вроде
(let ((classname (class-name class)))
(compile (generate-my-predicate-symbol classname)
(lambda (x) (typep x classname))))
Я думаю, что предложение Райнера написать свой собственный макрос DEFINE-CLASS-это путь, я имею в виду, как опытный шепелявый, скорее всего, сделает это, если нет никаких других соображений. Но я не очень опытный шепелявый, поэтому я могу ошибаться;)