2

How does one reliably create a lexically bound/scoped variable? Consider the following:

;; -*- lexical-binding: t; -*-
(let ((f load-file-name))
  (defun foo () f))

Normally f would be lexically bound (to the value of load-file-name at load time), but if anyone anywhere previously did (defvar f VALUE) then the code would break. (This is because f would become a special variable, and let preserves dynamic binding of special variables.)

How can I defend against such breakage?

(I don't expect anyone would ever do (defvar f VALUE), but someone might use defvar to define a variable with a name like argv or file-name which could potentially conflict with a let-bound variable.)

lexical-let seems to do what I want, but it is considered obsolete and it requires the use of the cl library. Is there no modern replacement?

6
  • 1
    As written, your explanation is not accurate as (defvar f) is different to (defvar f VALUE). The latter defines a special variable in the manner you've described. The former merely marks f as special for the purposes of the file in which that (defvar f) appears, such that uses of f in that same file will not treat it as lexical -- but it does not define that variable.
    – phils
    May 20 at 9:34
  • Outside of some historical core cases, special variables should always be hyphenated and therefore (by convention only) unhyphenated lexical variable names should not be subject to the problem you've described.
    – phils
    May 20 at 9:35
  • @phils Example for an exception of the "hypenation-rule": *this* in ob-core.el.
    – Tobias
    May 20 at 10:54
  • Ah, the "ear muffs" asterisks are a Common Lisp convention for naming special variables, so it would be a very sensible idea to not name any lexical variables that way either.
    – phils
    May 20 at 12:29

1 Answer 1

5
+50

Just use an uninterned symbol that cannot be reached outside of the let.
You need that uninterned symbol two times, one time for the let and one time within foo.
The purpose of the additional outer let-wrapper is to provide that uninterned symbol via make-symbol.

(let ((f-uninterned (make-symbol "f")))
  (eval
   `(let ((,f-uninterned load-file-name))
      (defun foo () ,f-uninterned))
   'lexical))

For the locally used symbol f-uninterned it is irrelevant whether lexical or dynamical binding is in effect.

So, with this snippet of Elisp, it is even irrelevant whether the buffer is loaded with lexical or dynamical binding. The eval with non-nil second argument ensures lexical binding for the contained backquoted form.

You get the same effect with the following elisp code. It works because a function argument (in our case the f of the lambda) is an unbound symbol:

;; -*- lexical-binding: t -*-

(funcall (lambda (&optional f)
       (setq f load-file-name)
       (defun foo () f)))

…or similar:

;; -*- lexical-binding: t -*-

(funcall (lambda (f)
       (defun foo () f))
     load-file-name)

Note, that using uninterned symbols is a well-known technique for macro programming, where it is even more critical, since there you are fighting local variables within user-defined functions (see the explanation about Macros in the Elisp manual).


If you are writing your own library, say mylib, an alternative would be to prefix the name of the lexically bound symbol with the library name such as in mylib-f.

When you are writing such a library you did already make sure that the library name is unique. Otherwise you would have problems on a higher level than that of your question.

Assuming the library name is unique, you have control over what the symbol mylib-f is used for.


When I wrote the second funcall solution I thought that this would allow an easy macro definition. Furthermore, your comment on dlet inspired me to search for an lexical equivalent llet. Voila see the comment in cconv.el. I think they dismissed that solution because it is only working with lexical binding.

I give here a more complete definition of llet and an additional suitable llet*:

;; -*- lexical-binding: t -*-

(defmacro llet (bindings &rest body)
  "Do like `let' but always use lexical binding.
This only works in libraries with lexical binding."
  (declare (indent 1)
       (debug ((&rest [&or symbolp (symbolp form)])
                   body)))
  `(funcall (lambda ,(mapcar
              (lambda (binding)
            (if (consp binding)
                (car binding)
              binding))
              bindings)
          ,@body)
        ,@(mapcar
           (lambda (binding)
         (when (consp binding)
           (cadr binding)))
           bindings)))

(defmacro llet* (bindings &rest body)
  "Do like `let*' but always use lexical binding.
This only works in libraries with lexical binding."
  (declare (indent 1)
       (debug ((&rest [&or symbolp (symbolp form)])
                   body)))
  (if bindings
      `(llet ,(list (car bindings))
     ,(macroexpand
       `(llet* ,(cdr bindings)
          ,@body)))
    `(progn ,@body)))
2
  • Fantastic and thorough answer, thank you! I find it surprising that dlet exists but we have to jump through these hoops to do the lexical equivalent of dlet. May 20 at 18:21
  • @RichardHansen I've just tried it with GNU Emacs 27.2 (build 1, x86_64-w64-mingw32) of 2021-03-26. Both funcall-versions run like expected. Reproduction: 1. Copy above stuff into a file "c:/temp/test.el". 2. Start new instance of Emacs. 3. Run M-: (load "/temp/test.el") 4. Run M-: (foo) 5. Run M-: (setq f 1) 6. Run M-: (foo). Both instances of (foo) return c:/temp/test.el.
    – Tobias
    May 20 at 20:41

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.