Friday, April 22, 2011

Lexical-let Corner Case

Emacs 24 will have actual lexical binding, but until that becomes the standard, we've got to satisfy ourselves with cl.el's lexical-let form, a codewalker which does some magical things to simulate lexical scope. Today I discovered the following corner case when combining lexical and dynamic scope on variables with the same name. Consider:

(setq x "Hi there")
(defun return-x () x)
(return-x) -> "Hi there"
(let ((x "Yo."))
  (return-x)) -> "Yo." ; Dynamic scope in action, 'x is shadowed.
(lexical-let ((x "Yo."))
  (return-x)) -> "Hi there." 
; lexical-let doesn't create a dynamic binding, so x
; is not shadowed
(lexical-let ((x "Yo."))
  (let ((x x))
    (return-x))) -> "Hi there", but should be "Yo."
; This is unexpected behavior.

What is the problem? The let expression in the last example is rewritten by the codewalker, which replaces the x in the binding form with a hidden name. This prevents x from being dynamically bound, even though the let should shadow the x inside return-x. I am not sure if there is a way around this that isn't incredibly baroque. Probably best to just be aware of it.

2 comments:

medyk said...

I've approached same issue recently. It seems that you cannot make lexically bound variables dynamic with let.
Workaround I found was to use lambda:

(lexical-let ((x "Yo"))
(funcall (lambda (x)
(return-x)) x))

J.V. Toups said...

Part of the issue is that, at least within a let within a lexical-let, the meaning of setq becomes ambiguous. Do you mean to setq the content of the lexically or the dynamically variable? Had I written the codewalker, I might have assumed that if the user explicitly reintroduces a dynamic scope, then they must mean to set the variable in that scope, but perhaps there is something about the implementation which makes this prohibitive.

I make extensive use of lexical let in a lot of my emacs code, and I've only had problems with this behavior once or twice. So I guess it doesn't matter that much? I will still be much happier when people are mostly using a lex-bind version of emacs.