Должно ли поведение функции зависеть от имени ее переменной?

Вот короткий код elisp, который показывает, что поведение функции зависит от имени ее переменной. Это ошибка?

Функция объявляется с использованием переменной x. Когда эта функция вызывается с переменной с именем, отличным от x, она работает как положено. Но если он вызывается с переменной с именем x, это не удастся!

Моя система - GNU Emacs 22.2.1 (powerpc-apple-darwin8.11.0, Carbon Version 1.6.0) от 2008-04-05 на g5.tokyo.stp.isas.jaxa.jp


Вставьте это в буфер emacs, поместите курсор после последнего parehthesis и нажмите \Cx\Ce, чтобы увидеть, что функция make-zero теперь работает правильно при вызове во второй раз.

(progn
  (defun make-zero (x) 
    "Simple function to make a variable zero."
    (set x 0))

  (setq x 10)

  (insert "\n Variable x is now equal to " (number-to-string x))

  (setq y 20)

  (insert "\n Variable y is now equal to " (number-to-string y))

  (insert "\n\n Let us apply make-zero to y")

  (make-zero 'y)

  (insert "\n Variable y is now equal to " (number-to-string y))

  (insert "\n\n Let us apply make-zero to x")

  (make-zero 'x)

  (insert "\n Variable x is now equal to " (number-to-string x))

  (insert "\n\n Why make-zero had no effect on x?  Is it because the name of the
variable in the definition of make-zero, namely 'x', is the same as the name of
the variable when make-zero was called?  If you change the name of the variable
in the definition of make-zero from x to z, this strange behaviour will
disappear.  This seems to be a bug in elisp."))

2 ответа

Это не ошибка, а природа динамического связывания Элиспа (и Лиспа в целом). ' не передает ссылку (то есть & в C/C++) он передает неоцененный символ; то, к чему это тогда оценивает, зависит от области, в которой это оценивается, что означает, что это получает x это находится внутри области действия функции.

В Lisp-думать нормальным способом было бы использовать макрос.

(defmacro make-zero (x) (list 'set x 0))

или же

(require 'cl)
(defmacro make-zero (x) `(set ,x 0))

Это не ошибка. Это будет стоить того, чтобы вы прочли руководство для Scoping Rules for Variable Bindings.

Функция, которую вы написали, вызывает set, который принимает символ (значение первого аргумента) и меняет его значение на значение второго аргумента. make-zero ты написал связывает x локально к его входному аргументу, поэтому, когда вы передаете символ xустановить изменения первой привязки для x он находит, что оказывается локальным связыванием.

Вот другой пример, скажем, у вас было следующее:

(defun print-something (something)
  (set 'something "NEW VALUE")
  (insert something))

(print-something "OLD")    ; inserts "NEW VALUE"

Глядя на этот фрагмент кода, имеет ли смысл, что set линия меняет локальное значение something?

Неважно, есть ли глобальная настройка для символа something,

Еще один пример:

(defvar x "some global value") ;# could have used setq here
(let ((x "local binding"))
  (set 'x "new value"))

Какую привязку вы ожидаете set линию менять? Тот, который создан let или глобальный, созданный defvar?

Функция, которую вы написали, выполняет (в значительной степени) то же самое, что и letвы создаете локальную привязку для переменной, которая видна перед глобальной.

Если вы хотите передать ссылку на переменную, то единственный безопасный способ сделать это - использовать макросы, которые я рекомендую, но не до тех пор, пока вы не освоите основы lisp (макросы b/c определенно более сложные). Тем не менее, не дай мне помешать тебе погрузиться в макросы, если это твоя страсть.

Хорошее введение в программирование Emacs lisp можно найти здесь.

Ответ geekosaur отлично показывает, как вы добьетесь того, чего хотите.

Другие вопросы по тегам