В Elisp, как получить доступ к ячейке значения символа, который связан локально из закрытия?
Как и в приведенном ниже коде, я определяю функцию для создания замыкания, которая принимает один аргумент, значение которого, как ожидается, будет символом, ссылающимся на переменную, связанную в контексте этого замыкания. В теле закрытия я использую symbol-value
, чтобы получить значение символа, но он вызывает ошибку, говоря Symbol's value as variable is void
, я ожидаю, что оценка этого фрагмента покажет 123
.
Итак, у меня есть два вопроса:
- Почему
symbol-value
не работает ? - Как исправить этот фрагмент, чтобы получить желаемый результат ?
(defun make-closure ()
(lexical-let ((var1 123))
(lambda (name)
;; How can I get the value cell of the symbol
;; specified by the argument "name" ?
;; This doesn't work.
(message-box (format "%s" (symbol-value name))))))
(let ((closure (make-closure)))
(funcall closure 'var1))
Обновлено:
На самом деле, я получил этот вопрос, когда писал какой-то игрушечный код, чтобы имитировать "объектно-ориентированный". Сначала это было что-то вроде этого (что аналогично второму коду ответа Стефана):
(defun new-person (initial-name)
(lexical-let* ((name initial-name)
(say-hi (lambda ()
(message-box (format "Hi, I'm %s" name))))
(change-name (lambda (new-name)
(setq name new-name))))
(lambda (selector &rest args)
(cond
((equal 'say-hi selector) (apply say-hi args))
((equal 'change-name selector) (apply change-name args))
(t (message-box "Message not understood"))))))
(let ((tony (new-person "Tony")))
(funcall tony 'say-hi)
(funcall tony 'change-name "John")
(funcall tony 'say-hi))
Но я чувствовал, что пункты "cond" своего рода "шаблонные", и я думал, что это возможно, можно использовать символ, переданный из аргумента, поэтому я изменил его на следующий, который больше не работает, но я не мог выяснить почему:
(defun new-person (initial-name)
(lexical-let* ((name initial-name)
(say-hi (lambda ()
(message-box (format "Hi, I'm %s" name))))
(change-name (lambda (new-name)
(setq name new-name))))
(lambda (selector &rest args)
(apply (symbol-value selector) args))))
Это означает, что мы не должны использовать символ для ссылки на a лексически связанная переменная в замыкании, подобном приведенному выше, так как имена из них при оценке не гарантируется быть такими же, как они есть написано в источнике ?
2 ответа:
Вы неправильно поняли несколько вещей здесь, но ключевая вещь заключается в том, что символ
var1
, который вы передаете с(funcall closure 'var1)
, является не символом в лексической среде, в которой определена ваша лямбда-функция.Макрорасширение лексико-грамматической формы поможет уточнить. Это:
(lexical-let ((var1 123)) (lambda (name) (message-box (format "%s" (symbol-value name)))))
Расширяется следующим образом:
(progn (defvar --cl-var1--) (let ((--cl-var1-- 123)) #'(lambda (name) (message-box (format "%s" (symbol-value name))))))
То есть макрос
Обратите внимание, что вы на самом деле не сделали ничего с этойlexical-let
переписывает имена символов, которые вы указываете в привязках, в неконфликтном виде. манера.var1
привязкой. Если бы вы это сделали, мы бы увидели дополнительные ссылки на--cl-var1--
в коде.Когда вы передаете символ
Все это так, как и должно быть. Природа лексической привязки заключается в том, что она влияет на код, написанный в этой области, и делает не воздействуйте на код снаружи. Формаvar1
этой функции, вы передаете каноническийvar1
, Не--cl-var1--
(или что бы это ни было на практике).(let ((closure (make-closure))) (funcall closure 'var1))
находится снаружи и поэтому не видит лексически связанногоvar1
вообще.
Когда дело доходит до" исправления " кода, я скорее пытаюсь понять, куда вы пытаетесь пойти с этим, но с моей интерпретацией вы вообще не хотите закрытия, потому что вы ищете динамическую привязку, а не лексическую привязку. например:
(defun make-func () (lambda (name) (message-box (format "%s" (symbol-value name))))) (let ((func (make-func)) (var1 123)) (funcall func 'var1))
Основываясь на правке вопроса, я бы предложил немного переработать код, чтобы что вы не используете лексическую привязку для значений, которые пытаетесь сопоставить аргументу функции. Например:
(defun new-person (initial-name) (lexical-let* ((name initial-name) (map (list (cons 'say-hi (lambda () (message-box (format "Hi, I'm %s" name)))) (cons 'change-name (lambda (new-name) (setq name new-name)))))) (lambda (selector &rest args) (apply (cdr (assq selector map)) args))))
Лексически связанные переменные принципиально не имеют имени (то есть их имя является лишь временным артефактом, присутствующим в исходном коде, но отсутствующим при вычислении).
Вместо этого можно использовать ссылку на переменную:
;; -*- lexical-binding:t -*- (defun make-closure () (lambda (ref) ;; How can I get the value cell of the symbol ;; specified by the argument "name" ? ;; This doesn't work. (message-box (format "%s" (gv-deref ref))))) (let ((closure (make-closure))) (let ((var1 123)) (funcall closure (gv-ref var1))))
Но обратите внимание, что мне пришлось переместить привязку let
var1
, потому что в противном случае я не могу получить ссылку на него извне.Другой вариант-вручную дать имя вашим лексическим переменным:
(defun make-closure () (lexical-let ((var1 123)) (lambda (name) ;; How can I get the value cell of the symbol ;; specified by the argument "name" ? ;; This doesn't work. (message-box (format "%s" (pcase name ('var1 var1) (_ (error "Unknown var name %S" name)))))))) (let ((closure (make-closure))) (funcall closure 'var1))
Обратите внимание, что я использовал
var1
для двух разных целей: один раз это имя лексического var, а в другой раз это просто символ, используемый для выбора, какой var использовать, иpcase
вещь переводит одно в другое: мы могли бы использовать "любое" другое имя для лексически связанного var, и код будет работать одинаково хорошо (без необходимости изменять внешний вызывающий объект).