В Elisp, как получить доступ к ячейке значения символа, который связан локально из закрытия?


Как и в приведенном ниже коде, я определяю функцию для создания замыкания, которая принимает один аргумент, значение которого, как ожидается, будет символом, ссылающимся на переменную, связанную в контексте этого замыкания. В теле закрытия я использую symbol-value, чтобы получить значение символа, но он вызывает ошибку, говоря Symbol's value as variable is void, я ожидаю, что оценка этого фрагмента покажет 123.

Итак, у меня есть два вопроса:

  1. Почему symbol-value не работает ?
  2. Как исправить этот фрагмент, чтобы получить желаемый результат ?
(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 2

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, и код будет работать одинаково хорошо (без необходимости изменять внешний вызывающий объект).