Emacs 24 added optional lexical bindings for local variables. I would like to use this functionality in my module, while maintaining compatibility with XEmacs and the previous Emacs versions.
Before Emacs 24, the easiest way to get closures was by using the lexical-let
form defined in cl-macs
, which emulated lexical scope with some clever macro trickery. While this was never hugely popular among elisp programmers, it did work, creating real and efficient closures, as long as you remembered to wrap them in lexical-let
, as in this pseudocode:
(defun foo-open-tag (tag data)
"Maybe open TAG and return a function that closes it."
... do the work here, initializing state ...
;; return a closure that explicitly captures internal state
(lexical-let ((var1 var1) (var2 var2) ...)
(lambda ()
... use the captured vars without exposing them to the caller ...
)))
The question is: what is the best way to use the new lexical bindings, while retaining support for Emacs 23 and for XEmacs? Currently I solved it by defining a package-specific macro that expands into lexical-let
or into ordinary let
depending on whether lexical-binding
is bound and true:
(defmacro foo-lexlet (&rest letforms)
(if (and (boundp 'lexical-binding)
lexical-binding)
`(let ,@letforms)
`(lexical-let ,@letforms)))
(put 'foo-lexlet 'lisp-indent-function 1)
... at the end of file, turn on lexical binding if available:
;; Local Variables:
;; lexical-binding: t
;; End:
This solution works, but it feels clunky because the new special form is non-standard, doesn't highlight properly, can't be stepped into under edebug
, and generally draws attention to itself. Is there a better way?
EDIT
Two examples of ideas for smarter (not necessarily good) solutions that allow the code to continue using standard forms to create closures:
Use an advice or a compiler macro to make lexical-let
expand to let
under lexical-bindings
iff the lexical-let
only assigns to symbols which are lexically scoped anyway. This advice would only be temporarily activated during byte-compilation of foo.el
, so that the meaning of lexical-let
remains unchanged for the rest of Emacs.
Use a macro/code-walker facility to compile let
of non-prefixed symbols to lexical-let
under older Emacsen. This would again only apply during byte-compilation of foo.el
.
Do not be alarmed if these ideas smell of overengineering: I am not proposing to use them as-is. I'm interested in the alternatives to the above macro where the package gets the benefit of nicer portable usage of closures for the price of some additional complexity of loading/compilation.
EDIT 2
As no one has stepped up with a solution that would allow the module to keep using let
or lexical-let
without breaking them for the rest of Emacs, I am accepting Stefan's answer, which states that the above macro is the way to do it. In addition to that, the answer improves on my code by using bound-and-true-p
and adding an elegant declaration for edebug and lisp-indent.
If someone has an alternative proposal for this compatibility layer, or an elegant implementation of the above ideas, I encourage them to answer.
Lexical binding was introduced to Emacs, as an optional feature, in version 24.1. We expect its importance to increase with time. Lexical binding opens up many more opportunities for optimization, so programs using it are likely to run faster in future Emacs versions.
A name that is lexically bound is looked up only in bindings in the lexical environment of the name – that is, in bindings that enclose the name in the source code. So if “a” is lexically bound, the code above prints “1”, because only binding (1) is in the lexical environment.
Since lexical-let
and lexical-binding's let
do not do quite the same (more specifically lexical-let
always uses lexical binding, whereas let
uses either dynamic binding or lexical binding depending on whether the var was defvar
'd or not), I think your approach is about as good as it gets. You can easily make Edebug step into it, tho:
(defmacro foo-lexlet (&rest letforms)
(declare (indent 1) (debug let))
(if (bound-and-true-p lexical-binding)
`(let ,@letforms)
`(lexical-let ,@letforms)))
If you don't want to depend on declare
, you can use (put 'foo-lexlet 'edebug-form-spec 'let)
.
One possible solution is to use defadvice
to hook lexical-let
expansion. I wrote the following advice, and it seems to work fine. This is also byte-compile
aware.
(defadvice lexical-let (around use-let-if-possible (bindings &rest body) activate)
(if (and (>= emacs-major-version 24)
(boundp 'lexical-binding)
lexical-binding)
(setq ad-return-value `(let ,bindings . ,body))
ad-do-it))
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With