Как уже указывалось, решение состоит в том, чтобы использовать intern
вместо make-symbol
.
Можно создать несколько независимых символов с одним и тем же именем, но только один из них может быть каноническим символом для данного имени - т.е. символ, который вы получите, когда будете ссылаться на него в другом месте.
intern
возвращает канонический символ для данного имени. Он создает новый символ только , если интернированный символ с таким именем уже не существует. Это означает, что он будет только когда-либо создавать один символ для любого данного имени 1 .
make-symbol
, с другой стороны, создает новый символ каждый раз, когда он вызывается. Это не встроенные символы - каждый из них является полностью действительным и функциональным символом, но не тот, который будет отображаться при обращении к символу по его имени.
См .: C-h i g (elisp) Creating Symbols
RET
Поскольку defun
возвращает установленный символ, вы можете наблюдать за происходящим, захватывая возвращаемое значение и используя его как функцию:
(defalias 'foo (deftext "ni" "What is the flight speed velocity of a laden swallow?"))
M-x text-ni ;; doesn't work
M-x foo ;; works
или аналогично:
(call-interactively (deftext "shrubbery" "It is a good shrubbery. I like the laurels particularly."))
Сложная часть всего этого - и причина, по которой оценка расширенной формы выполняла то, что вы хотели, а макрос - нет - связана именно с тем, как и когда (или действительно , если ), читатель lisp преобразует название функции в символ.
Если мы напишем функцию foo1:
(defun foo1 (texttoinsert) (insert-string texttoinsert))
Читатель lisp читает это как текст и преобразует его в объект lisp. Мы можем использовать read-from-string
, чтобы сделать то же самое, и мы можем посмотреть на печатное представление результирующего объекта lisp:
ELISP> (car (read-from-string "(defun foo1 (texttoinsert) (insert-string texttoinsert))"))
(defun foo1
(texttoinsert)
(insert-string texttoinsert))
Внутри этого объекта имя функции является каноническим символом с именем «foo1». Однако обратите внимание, что возвращаемое значение, которое мы видим из read-from-string
, является только напечатанным представлением этого объекта, и канонический символ представлен только его именем. Печатное представление не позволяет нам различать интернированные и не интернированные символы, так как все символы представлены только их именем.
(Забегая вперёд, это источник вашей проблемы при оценке расширения вашего макроса напечатано , так как эта печатная форма передается через читатель lisp, и то, что когда-то было непостоянным символом, становится интернированный символ.)
Если мы затем перейдем к макросам:
(defmacro deffoo2 ()
`(defun foo2 (texttoinsert) (insert-string texttoinsert)))
ELISP> (car (read-from-string "(defmacro deffoo2 ()
`(defun foo2 (texttoinsert) (insert-string texttoinsert)))"))
(defmacro deffoo2 nil
`(defun foo2
(texttoinsert)
(insert-string texttoinsert)))
На этот раз читатель прочитал определение macro в объект lisp, и внутри этого объекта находится канонический символ foo2
. Мы можем убедиться в этом, осмотрев объект напрямую:
ELISP> (eq 'foo2
(cadr (cadr (nth 3
(car (read-from-string "(defmacro deffoo2 ()
`(defun foo2 () (insert-string texttoinsert)))"))))))
t
Итак, для этого макроса он уже имеет дело с этим каноническим символом foo2
до , когда происходит любой вызов / расширение макроса, потому что средство чтения lisp установило это при чтении самого макроса. В отличие от нашего предыдущего простого определения функции (в котором символ функции был определен читателем lisp как функция была определена), когда макрос вызывается и расширяется, читатель lisp используется , а не . Расширение макроса выполняется с использованием уже существующих объектов LISP - чтение не требуется.
В этом примере символ функции уже присутствует в макросе, и поскольку он является каноническим символом, мы могли бы использовать (foo2)
в другом месте нашего кода для вызова этой функции. (Или, если бы я сделал определение интерактивным, используйте M-x foo2
.)
Наконец, возвращаясь к исходному макросу из вопроса, очевидно, что lisp reader никогда не встречает символ имени функции для функции, которую он определит:
ELISP> (car (read-from-string "(defmacro deftext (functionname texttoinsert)
`(defun ,(make-symbol (concatenate 'string \"text-\" functionname)) ()
(interactive)
(insert-string ,texttoinsert)))"))
(defmacro deftext
(functionname texttoinsert)
`(defun ,(make-symbol
(concatenate 'string "text-" functionname))
nil
(interactive)
(insert-string ,texttoinsert)))
Вместо этого объект, созданный считывателем lisp, содержит выражение ,(make-symbol (concatenate 'string "text-" functionname))
; и это выражение в кавычках будет оценено во время раскрытия, чтобы создать новый не встроенный символ, который будет частью объекта, созданного этим расширением.
В наших предыдущих примерах получившийся объект имел автомобиль defun
(интернированный) и cadr foo1
или foo2
(оба также интернированные).
В этом последнем примере объект имеет автомобиль с defun
(интернированным), но кадром с непостоянным символом (имя которого является результатом выражения concatenate
).
И, наконец, если вы распечатаете этот объект, печатное представление этого символа непостоянного символа будет именем символа, и чтение этого напечатанного представления обратно путем его оценки вызовет вместо него должна быть определена функциональная ячейка для канонического символа .
1 Фактически функцию unintern
можно использовать для удаления символа, после чего вызов intern
для того же имени * естественным образом создаст новый символ; но это не важно для этого обсуждения.