This answer is an extension of Phils answer.
Your question shows the potential danger of locally binding special-declared variables.
The convention of prefixing symbol names with library names normally protects us from accidentally using special-declared symbols for local bindings that should have lexical scope.
But there are exceptions like displayed-month
and displayed-year
from calendar.el
.
One can ensure lexical binding with the help of uninterned symbols.
The principle is described through a simple documented example:
;; -*- lexical-binding: t -*-
(require 'cl)
;; reset:
(unintern 'wtf)
(unintern 'foo)
;; declare `wtf' as special:
(defvar wtf)
(setq wtf 0)
(defmacro f ()
(let ((sym (make-symbol "wtf"))) ;; `make-symbol' returns an uninterned symbol with name `wtf'
`(let ((,sym 1)) ;; We substitute here the uninterned symbol `wtf'.
(lambda ()
,sym))))
(setq ans (funcall (f)))
;; => 1
It is clear that the definition of the macro f
in the above example is cumbersome.
But a special lexlet
macro as defined in the following can relieve us from that work.
First comes the library code defining the lexlet
macro and below that the user code resembling the simplified introductory example.
The macro lexlet
generates uninterned duplicates of all locally bound symbols, locally binds the uninterned symbols to their given values and replaces the locally bound interned symbols in the body with their uninterned counterparts.
The definition of the macro is kept simple. But there is a small caveat. One cannot use the function definition of the locally bound symbols.
;; -*- lexical-binding: t; -*-
;; Here starts the library code.
(defun lexlet--search (data symbol-alist)
"Replace symbols according to SYMBOL-ALIST in DATA."
(cond
((symbolp data)
(or
(alist-get data symbol-alist)
data))
((vectorp data)
(apply #'vector (seq-map #'lexlet--search data symbol-alist)))
((consp data)
(cons (lexlet--search (car data) symbol-alist)
(lexlet--search (cdr data) symbol-alist)))
(t data)))
;; Test:
;; (lexlet--search '(lambda () wtf) '((wtf . var)))
(defmacro lexlet (bindings &rest body)
"Like `let' but with lexical binding for all symbols in BINDINGS.
If a symbol has a local binding in BINDINGS its binding during
the execution of BODY is lexical even if the symbol is declared as special.
This macro requires `lexical-binding' set to t."
(declare (indent 1)
(debug ((&rest (symbol sexp)) body)))
(let* ((symbol-alist (mapcar
(lambda (binding)
(let ((symbol (car binding)))
(cons
symbol
(make-symbol (symbol-name symbol)))))
bindings))
(new-bindings (mapcar
(lambda (binding)
(let* ((old-symbol (car binding))
(new-symbol (alist-get old-symbol symbol-alist)))
(cons new-symbol (cdr binding))))
bindings)))
(append
(list
#'let
new-bindings)
(lexlet--search body symbol-alist))))
(provide 'lexlet)
With lexlet
the user code of the introductory example looks friendlier:
(require 'lexlet)
(defvar wtf)
(setq wtf 0)
(defun f ()
(lexlet ((wtf 1))
(lambda ()
wtf)))
(setq ans (funcall (f)))
;; => 1
It is important that the macro extends the lexical environment created by an outer let
.
For an example eval
with an environment given as argument lexical
does not extend an outer lexical environment and is therefore only of limited use for our purpose.
But, lexlet
is okay in that regard:
;; -*- lexical-binding: t -*-
(require 'lexlet)
;; reset:
(unintern 'wtf)
(unintern 'lexbound)
(defvar wtf)
(setq wft 0)
(defun f ()
(let ((lexbound 1))
(lexlet ((wtf 2))
(lambda ()
(list
lexbound wtf)))))
(setq ans (funcall (f)))
;; => (1 2)