Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Lexical scope in Emacs: compatibility with older Emacsen

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.

like image 295
user4815162342 Avatar asked Sep 17 '12 09:09

user4815162342


People also ask

What is lexical binding Emacs?

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.

What is lexically bind?

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.


2 Answers

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

like image 85
Stefan Avatar answered Nov 09 '22 11:11

Stefan


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))
like image 34
m2ym Avatar answered Nov 09 '22 12:11

m2ym