3

I have a macro that intends to creates a closure:

; -*- lexical-binding: t -*-
(defmacro repro ()
  (let ((kmap-sym (gensym "kmap-")))
    `(let ((,kmap-sym (make-sparse-keymap)))
       (define-key ,kmap-sym "a"
         (lambda () (interactive) (message "kmap is %s" ,kmap-sym)))
       ,kmap-sym)))

;; only works when lexical-binding: t in the current file
(funcall (lookup-key (repro) "a"))

Although lexical-binding is t in the file where the macro is defined, (funcall (lookup-key (repro) "a")) fails with the error (void-variable kmap-30252) whenever it is evaluated in a different file where lexical-binding is nil.

Since I can't control where the macro will be used, I considered going back to using lexical-let for the inner let in the example, which does work regardless of where the macro is invoked.

(defmacro repro ()
  (let ((kmap-sym (gensym "kmap-")))
    `(lexical-let ((,kmap-sym (make-sparse-keymap)))
       (define-key ,kmap-sym "a"
         (lambda () (interactive) (message "kmap is %s" ,kmap-sym)))
       ,kmap-sym)))

;; this works in any file
(funcall (lookup-key (repro) "a"))

But lexical-let requires cl, which I would like to avoid, and unfortunately lexical-let is not defined in cl-lib.

What options do I have for creating a closure?

Should the macroexpansion of repro always expand into lexical bindings given that lexical-binding is t where the macro is defined?

1

Not very sure if the result meets your expectation.

(defun foo (keymap)
  `(lambda () (interactive) (message "kmap is %s" ',keymap)))

(defmacro repro ()
  (let ((kmap-sym (gensym "kmap-")))
    `(let ((,kmap-sym (make-sparse-keymap)))
       (define-key ,kmap-sym "a"
         (foo ,kmap-sym))
       ,kmap-sym)))

(setq lexical-binding nil)
;; => nil

(funcall (lookup-key (repro) "a"))
;; => "kmap is (keymap (97 lambda nil (interactive) (message kmap is %s (quote #0))))"

(setq lexical-binding t)
;; => t

(funcall (lookup-key (repro) "a"))
;; => "kmap is (keymap (97 lambda nil (interactive) (message kmap is %s (quote #0))))"

The following does the same without using a top-level defun.

(defmacro repro ()
  (let ((kmap-sym (gensym "kmap-"))
        (fun (lambda (keymap)
               `(lambda () (interactive) (message "kmap is %s" ',keymap)))))
    `(let ((,kmap-sym (make-sparse-keymap)))
       (define-key ,kmap-sym "a"
         (,fun ,kmap-sym))
       ,kmap-sym)))
  • I was considering something like this based on the way one can create a javascript closure. But I wasn't sure if there was another alternative. I was also hoping to provide this closure anonymously instead of relying on a top-level function. – erjoalgo Nov 12 '18 at 22:35
  • @erjoalgo You can put the top-level function into the macro, (let ((f (lambda (keymap) `(lambda () ...))))), it does the same. – xuchunyang Nov 12 '18 at 22:51
3

Your example is likely not representative of the actual code you're using, but a good solution might be to create the closure in the macro and return the closure, instead of returning code which may or may not turn into a closure depending on lexical-binding:

(defmacro repro ()
  (let* ((kmap-sym (gensym "kmap-")))
    `(let ((,kmap-sym (make-sparse-keymap)))
       (define-key ,kmap-sym "a"
          ',(lambda () (interactive) (message "kmap is %S" (symbol-value kmap-sym))))
       ,kmap-sym)))

The other option I use nowadays is to do something like

(defmacro ...
  (if (not lexical-binding)
      (error "Macro `foo` can't be used with dynamic binding"))
    ...))

the variable lexical-binding can be relied upon (while expanding the macro) to indicate whether the returned code will be run with lexical or dynamic binding.

  • I tried this solution pastebin.com/8ntq4fTJ but it still failed for me with "Symbol’s value as variable is void: kmap-sym" – erjoalgo Nov 27 '18 at 9:51
  • @erjoalgo: Was this code placed in a file that uses lexical-binding? – Stefan Nov 27 '18 at 16:31
  • yes (insert filler chars) – erjoalgo Dec 2 '18 at 9:15
2

There is an ugly workaround using eval with the optional argument LEXICAL set to t.

(defmacro repro ()
  (let ((kmap-sym (gensym "kmap-")))
    `(eval
      '(let ((,kmap-sym (make-sparse-keymap)))
         (define-key ,kmap-sym "a"
           (lambda () (interactive) (message "kmap is %s" ,kmap-sym)))
         ,kmap-sym)
      t)))

It is ugly since the associated quoting prevents byte-compilation.

  • it's nice to know this exists, although I'd prefer (eval-when-compile (require 'cl-lib)) over this. – erjoalgo Nov 12 '18 at 21:32

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.