I am trying to convert my code to use lexical binding.
I have a function (format-template
) that calls a lambda function (let-bound to replacer-inner
) from a user-customizable list (template-replace-functions
) of functions based on a match-string. The replacer-inner function takes no arguments and returns a string, however, it may need to use variables foo
bar
and/or baz
, which are passed as part of the argument list
to format-template
.
(setq lexical-binding t)
(defcustom template-replace-functions
'(("email"
(lambda () user-mail-address)
("apples"
(lambda ()
(cond ((= foo 1)
"one")
((= foo 2)
"two")
((= foo 3)
"three")
(t "four"))))
("bananas"
(lambda ()
(if (eq bar 'move)
"movement" "sit still")))))
"Association list of replacement functions.
For each STRING, the corresponding FUNCTION is called with no
arguments and must return a string."
:type '(repeat (group string function))
:group 'spice-girls)
(defun format-template (list)
(let* ((foo (nth 0 list))
(bar (nth 1 list))
(baz (nth 2 list))
template
(replacer-outer
(lambda ()
(replace-regexp-in-string
"\${\([^\s\t\n]*?\)}"
(lambda (match)
(let* ((key (match-string 1 match))
(replacer-inner
(cadr (assoc key template-replace-functions))))
(if (and replacer-inner
(stringp (funcall replacer-inner)))
(funcall replacer-inner) "")))
template t t))))
(setq template
...)
(if template (funcall replacer-outer))))
So, for example, list
contains three elements, which are let-bound to foo
bar
and baz
. The key
of the match-string is "bananas"
which associates to a function in template-replace-functions
and sets replace-inner
to:
(lambda ()
(if (eq bar 'move)
"movement" "sit still"))
At this point, the above lambda function needs to know bar
, which is fine using dynamic binding, but not so fine using lexical binding.
My question is, how do I do this to allow the lambda function bound to replacer-inner
take from the let-bound values of foo
bar
and baz
?
(Just to confuse things, the replace-outer
function, which contains replace-inner
, is let-bound because it is called from one of two places in the real function. I could have written inline here but have included it this way in case it adds to the problem.)
Edit:
Halfway there...
(setq lexical-binding t
foo 0
bar 0)
(setq inline-fun-1
'(lambda ()
(setq return
(if (eq foo 1)
"Pass" "Fail"))))
(defmacro lex-fun ()
`(let* ((foo 1)
(bar 1)
return)
(funcall ,inline-fun-1)))
(lex-fun) ; -> "Pass"
(defun inline-fun-2 ()
(setq return
(if (eq bar 1)
"Pass" "Fail")))
(defmacro lex-fun ()
`(let* ((foo 1)
(bar 1)
return)
(funcall ,inline-fun-2)))
(lex-fun) ; -> Lisp error: (void-variable inline-fun-2)
So, expanding the lambda function within a macro appears to work, but not a named function. But I do want to allow the user lambda or named functions. How to get around this?
(lambda () user-mail-address)
is not a function. Instead, it's a list of 3 elements, which happens to look the same as the source code for a function. And Emacs is nice enough to turn one into the other if you pass that list tofuncall
. But if you really want to say that it's a function, then you need to write `(("email" ,(lambda () user-mail-address) ...