emacs lisp - eval неожиданно отличается от eval-print-last-sexp - PullRequest
4 голосов
/ 21 марта 2012

прости мое многословие. Я не совсем уверен, как описать мою ситуацию.

Учитывая следующее ...

(defun three () 3)
(defun four () 4)
(defun makeplusser (x)
  (list 'defun (make-symbol (format "%s+" x)) '(y) 
    (list '+ (list x) 'y)))

В моем чистом буфере я набираю следующее, затем нажимаю C-j (eval-print-last-sexp) ...

(makeplusser 'three)

Что дает мне это ...

(defun three+ (y) (+ (three) y))

... Что я выделяю, нажимаю C-j и могу использовать ...

(three+ 4) ; => 7

Когда я делаю это ...

(eval (makeplusser 'four)) ; => four+

Он выводит имя функции " four + " в буфер, что наводит меня на мысль, что оно должным образом не определено. Когда я пытаюсь использовать это ...

(four+ 3)

Я получаю следующее сообщение об ошибке:

Введен отладчик - Ошибка Lisp: (void-функция four +) (four + 3)
eval ((четыре + 3))

Вопросы:

  1. Почему (eval-print-last-sexp) ведет себя так иначе, чем при использовании (eval)?
  2. Как я могу получить свои функции "plusser", не оценивая сгенерированный defun в моем буфере? Я хотел бы иметь возможность просто (mapcar # 'eval (mapcar #' makeplusser '(три четыре))) или что-то еще, но это не работает так, как я ожидаю.

Ответы [ 2 ]

8 голосов
/ 21 марта 2012

1- Ваша проблема не вызвана различиями между eval-print-last-sexp и eval.

Например, если вы eval следующий фрагмент (например, с использованием Cx Ce ), все работает

(setq exp (list 'defun 'four+ '(y) (list '+ '(four) 'y)))
(eval exp)
(four+ 4)

, тогда как при оценке таким же образом следующий фрагмент не работает должным образом:

(setq exp (makeplusser 'four))
(eval exp)
(four+ 4)

даже при значении exp одинаково в обоих случаях

2- Фактическая проблема, с которой вы сталкиваетесь, связана с тем, как вы создаете символ для своей функции: make-symbol создает непостоянный символ, который будетне быть видимым снаружи вызова функции.Вместо этого вы должны создать интернированный символ, например:

(defun makeplusser (x)
  (list 'defun (intern (format "%s+" x)) '(y) 
    (list '+ (list x) 'y)))
(eval (makeplusser 'four))

3- Для таких вещей вы должны рассмотреть возможность написания макроса вместо оценки результата функции:

(defmacro makeplusser (x)
  (let ((name   (intern (format "%s+" x)))
        (argsym (make-symbol "arg")))
    `(defun ,name (,argsym)
       (+ (,x) ,argsym))))
6 голосов
/ 21 марта 2012

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

...