Search code examples
emacslispelispemacs24lexical-scope

Emacs: the code in the body of a defun or defmacro cannot refer to surrounding lexical variables?


Update 2013 May: As of GNU Emacs 24.3.1, (let .. (defun..)) bytecompiles just fine without warning and the bytecompiled code works the same as not-compiled code. Just don't forget to add the file variable lexical-binding: t to the file to be bytecompiled. Workarounds at the end of this question is now not necessary.


Lexical Binding - Emacs Lisp Manual has this paragraph:

Note that functions like symbol-value, boundp, and set only retrieve or modify a variable's dynamic binding (i.e. the contents of its symbol's value cell). Also, the code in the body of a defun or defmacro cannot refer to surrounding lexical variables.

I am not sure if I am getting the meaning of the second sentence right. In the following code which should be run in lexical binding mode, the code in the body of a defun is successfully referring to the lexical binding value of the name n.

(let ((n 0))
  (defun my-counter ()
    (incf n)))

(my-counter) ;; 1
(my-counter) ;; 2

Is the sentence simply saying that (let .. (defun ..)) is a bad practice?


Workarounds:

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

;; a way to define the counter function without byte-compile error or warning

(defvar my--counter-func
  (let ((n 0))
    (lambda ()
      (setq n (1+ n)))))

(defun my-counter ()
  (funcall my--counter-func))

;; another way to define the counter function, again without byte-compile error or warning

(fset 'my-another-counter
      (let ((n 0))
        (lambda ()
          (setq n (1+ n)))))

And here's the code for testing the above code:

;; run:
;; emacs -q --load path-to-the-el-file-of-this-code.el

(load "path-to-file-defining-my-counter.elc") ;; loading the ELC file to test if byte-compiled code runs as expected.

(print (my-counter)) ;; 1
(print (my-counter)) ;; 2

(print (my-another-counter)) ;; 1
(print (my-another-counter)) ;; 2

Solution

  • The code does not byte-compile well at least in Emacs 24.1.1. I saved the following code in the foo.el file, which uses setq in place of incf in order to avoid any possible effects by the cl library:

    ;; -*- lexical-binding: t -*-
    
    (let ((n 0))
      (defun my-counter ()
        (setq n (1+ n))))
    

    When I tried to byte-compile it (M-x byte-compile-filefoo.el), I got the following warning messages:

    foo.el:3:1:Warning: Function my-counter will ignore its context (n)
    foo.el:3:1:Warning: Unused lexical variable `n'
    foo.el:5:11:Warning: reference to free variable `n'
    foo.el:5:17:Warning: assignment to free variable `n'
    

    All of the messages are indicating that the code in the body of the defun construct cannot refer to the surrounding lexical variable n as the manual claims.

    Actually, when I loaded the byte-compiled code (M-x load-filefoo.elc) and evaluted the (my-counter) form, I got the following erorr:

    Debugger entered--Lisp error: (void-variable n)
      ...
    

    Unfortunately, I'm not sure why the code appears to work when evaluated in the form of source code.