Макрос, определяющий функции, имена которых основаны на аргументах макроса

*Примечание: несмотря на то, что я часто посещал 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-это путь, я имею в виду, как опытный шепелявый, скорее всего, сделает это, если нет никаких других соображений. Но я не очень опытный шепелявый, поэтому я могу ошибаться;)