Можете ли вы создавать интерактивные функции в макросе Emacs Lisp? - PullRequest
8 голосов
/ 19 марта 2009

Я пытаюсь написать макрос в emacs lisp для создания некоторых "вспомогательных функций".

В конечном счете, мои вспомогательные функции будут более полезными, чем то, что я имею здесь. Я понимаю, что могут быть лучшие / более интуитивно понятные способы сделать то же самое (пожалуйста, напишите), но мой основной вопрос: почему не будет этой работы / что я делаю неправильно:

(defmacro deftext (functionname texttoinsert)
  `(defun ,(make-symbol (concatenate 'string "text-" functionname)) ()
     (interactive)
     (insert-string ,texttoinsert)))

(deftext "swallow" "What is the flight speed velocity of a laden swallow?")
(deftext "ni" "What is the flight speed velocity of a laden swallow?")

Если я возьму выходные данные макроэкспанда и оценим его, я получу интерактивные функции, которые я собирался получить с помощью макроса, но даже если макрос запускается и выглядит для оценки, я не могу вызвать M-x text-ni или text-swallow.

Ответы [ 4 ]

10 голосов
/ 19 марта 2009

Это делает то, что вы хотите:

(defmacro deftext (functionname texttoinsert)
  (let ((funsymbol (intern (concat "text-" functionname))))
`(defun ,funsymbol () (interactive) (insert-string ,texttoinsert))))
4 голосов
/ 28 января 2014

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

3 голосов
/ 28 января 2014

FWIW, если вы используете lexical-binding, вам не нужно использовать макрос:

(defun deftext (functionname texttoinsert)
  (defalias (intern (concat "text-" functionname))
    (lambda ()
      (interactive)
      (insert-string texttoinsert))))
1 голос
/ 19 марта 2009

Прошли годы, но я думаю, что вам, вероятно, не хватает fset для определения функции; см. документы , если вы хотите, чтобы он тоже был во время компиляции.

...