2

As in the code below, I define a function to create a closure that accepts one argument, the value of which is expected to be a symbol referring to a variable bound in the context of this closure. In the body of the closure, I use symbol-value to get the symbol's value, but it prompts an error saying Symbol's value as variable is void, I expect evaluating this snippet to show 123.

So here I have two questions:

  1. Why symbol-value doesn't work ?
  2. How do I correct this snippet to get the desired result ?
(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))

Updated:

Actually, I got this question when I was writing some toy code to imitate "Object Oriented". At first it was something like this (which is similar to the second code of Stefan's answer):

(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))

But I felt the clauses of "cond" kinda "boilerplate" and I thought it might be possible to use the symbol passed from the argument, so I modified it to the following, which no longer works, but I couldn't figure out why:

(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))))

So is that to say that we shouldn't use a symbol to reference a lexically-bound variable in a closure like the above, since the names of them at evaluation aren't guranteed to be the same as they're written in the source ?

louiet
  • 33
  • 2
  • 6
  • Lexical variables simply aren't intended to be accessible from outside of the closure. That's more or less the point of them. If you need to access those variables, then lexical binding was just the wrong choice in the first place, and I don't think that trying to find ways around that is a good idea. – phils Nov 23 '17 at 12:05

2 Answers2

2

You've misunderstood a few things here, but the key thing is that the var1 symbol you're passing with (funcall closure 'var1) is not the symbol in the lexical environment in which your lambda function is defined.

Macro-expanding the lexical-let form will help to clarify. This:

(lexical-let ((var1 123))
  (lambda (name)
    (message-box (format "%s" (symbol-value name)))))

is expanded along these lines:

(progn
  (defvar --cl-var1--)
  (let ((--cl-var1-- 123))
    #'(lambda (name)
        (message-box (format "%s" (symbol-value name))))))

Which is to say that the lexical-let macro re-writes the symbol names you specify in the bindings in a non-conflicting manner.

Note that you haven't actually done anything with that var1 binding. If you had done, we would see additional references to --cl-var1-- in the code.

When you pass the symbol var1 to this function, you are passing the canonical var1, not --cl-var1-- (or whatever it ended up being in practice).

This is all as it should be. The nature of lexical binding is that it affects the code written within that scope, and does not affect code outside. The (let ((closure (make-closure))) (funcall closure 'var1)) form is outside, and therefore does not see the lexically-bound var1 at all.


When it comes to "correcting" the code, I'm rather struggling to figure out where you are trying to go with this, but with my interpretation you wouldn't want a closure at all, because you're looking for dynamic binding rather than lexical binding. e.g.:

(defun make-func ()
  (lambda (name)
    (message-box (format "%s" (symbol-value name)))))

(let ((func (make-func))
      (var1 123))
  (funcall func 'var1))

Based on the edit to the question, I would suggest reworking the code slightly so that you are not using lexical binding for the values you're trying to match to the function argument. For instance:

(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))))
phils
  • 71,335
  • 11
  • 153
  • 198
  • +1 for the last paragraph and final code, and for mentioning that the question is not so clear wrt intention. – Drew Nov 22 '17 at 16:59
1

Lexically-bound variables fundamentally don't have a name (i.e. their name is only a temporary artifact present in the source code, but absent during evaluation).

You can instead use a reference to the variable:

;; -*- 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))))

But note that I had to move the let binding of var1 because otherwise I can't get a ref to it from the outside.

Another option is for you to manually give a name to your lexical variables:

(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))

Note that I used var1 for two different purposes: once it's the name of a lexical var, and the other time it's just a symbol used to select which var to use and the pcase thingy translates one to the other: we could use "any" other name for the lexically-bound var and the code would work equally well (without having to change the external caller).

Stefan
  • 27,908
  • 4
  • 53
  • 82