Отказ от ответственности: я не знаю Emacs Lisp, но я знаю Lisp.
Я полагаю, что вы сталкиваетесь с путаницей чтения / печати с непереданными символами.То есть, я подозреваю, что (make-symbol ...)
в Emacs Lisp, точно так же, как функция с тем же именем в других диалектах, таких как Common Lisp, создает новый символьный объект, который не имеет никакого отношения к сканируемому символу с тем же именемиз печатной записи (из файла, терминала, строки, буфера редактирования, ...).
Ваш код работает, когда вы получаете печатный вывод вашей функции генерации кода (он же S-выражение), потому чтонапечатанная нотация символа four
считывается обратно в Лисп и интернируется, в результате чего эта нотация становится тем же объектом, что и имя функции four
.
Но (make-symbol "four")
- это другой символобъект.Когда вы используете eval
в коде, который выходит из вашей функции, вы используете структуру данных напрямую, поэтому ваша ошибка не скрывается за счет сокращения кода до текста и чтения его обратно.Символ не преобразуется в токен four
и обратно в объект four
через интернирование.eval
увидит ваш исходный символ, полученный из make-symbol
: тот же самый машинный указатель на тот же фрагмент памяти.
(В Common Lisp обычно выводится «неинтернизированный символ», полученный из (make-symbol "four")
с нотацией хэш-точки, например #:four
, чтобы вы могли их заметить (на самом деле #:
означает символ без домашнего пакета, не содержащий ничего общего, но это очень неясная тонкость Common Lisp.))
В любом случае,ищите функцию с именем intern
.(intern "four")
будет искать существующий символ с этим именем и возвращать его, а не создавать новое.
;; two symbol interns for same name result in the same object
;; we are comparing the same pointer to itself
(eq (intern "foo") (intern "foo")) -> t
;; two symbol constructions result in two different object
;; two different pointers to separately allocated objects
(eq (make-symbol "foo") (make-symbol "foo")) -> nil
Также:
Вы действительно должны изучить обратную кавычку, если хотите написатькод, генерирующий код.В противном случае вы делаете это способом 1960-х годов, а не современным 1970-м:
;; don't let your friends do this:
(defun makeplusser (x)
(list 'defun (make-symbol (format "%s+" x)) '(y)
(list '+ (list x) 'y)))
;; teach them this:
(defun makeplusser (x)
`(defun ,(intern (format "%s+" x)) (y)
(+ (,x) y)))
;; even more clearly, perhaps
(defun makeplusser (func-to-call)
(let ((func-name (format "%s+" x)))
`(defun ,func-name (arg)
(+ (,func-to-call) arg))))
Когда использовать make-symbol
Используйте make-symbol
, когда вам нужно создать гарантированный уникальныйсимвол (не тот же объект, что и любой другой символ), даже если он имеет то же имя, что и другие символы.Другие диалекты Лиспа также имеют функцию, называемую gensym
, которая похожа на make-symbol
, но она также добавляет к имени возрастающую числовую отметку, чтобы упростить распознавание этих «генсимов», когда несколько встречаются в одном контексте.gensym
гораздо чаще используется, чем make-symbol
в Лиспах, которые его имеют.Уникальные символы полезны при генерации кода (макросы) для создания уникальных меток для вещей, которые должны быть абсолютно невидимы для окружающего кода, таких как временные локальные переменные во вставленном блоке кода.Аргументом вашей функции должен быть уникальный символ:
;; even more clearly, perhaps
(defun makeplusser (func-to-call)
(let ((func-name (format "%s+" x))
(arg-sym (make-symbol "arg"))
`(defun ,func-name (,arg)
(+ (,func-to-call) ,arg))))
Причина в том, что Emacs Lisp динамически ограничен.Если мы вызываем аргумент y
, существует риск, что вызываемая пользовательская функция, такая как (four)
, может содержать ссылку на y
, где намерение программиста состоит в том, чтобы достичь своей собственной переменной y
.Но ваша сгенерированная функция случайно захватывает ссылку!
Используя аргумент gensym, мы избегаем этой проблемы;нет никаких шансов, что код пользователя может ссылаться на аргумент: мы достигли «гигиены» или «прозрачности».