在过去,Emacs不支持词法范围。我想知道当时人们如何处理动态范围的特定陷阱。
假设Alice写了一个命令my-insert-stuff
,它依赖于fp-repeat
中定义的fp.el
函数(我们假设它是一个为Bob编写的函数式编程提供大量函数的库)并且假设fp-repeat
用于多次重复调用函数。
Alice的init.el
部分内容:
(require 'fp)
(defun my-insert-stuff ()
(interactive)
;; inserts "1111111111\n2222222222\n3333333333" to current buffer
(dolist (i (list "1" "2" "3"))
(fp-repeat 10
(lambda ()
(insert i)))
(insert "\n")))
来自Bob的fp.el
的部分内容:
(defun fp-repeat (n func)
"Calls FUNC repeatedly, N times."
(dotimes (i n)
(funcall func)))
爱丽丝很快发现她的命令不像她预期的那样有效。这是因为Alice使用i
和Bob使用i
发生冲突。在过去,Alice和/和Bob可以做些什么来防止这种碰撞发生?
也许鲍勃可以将文档字符串更改为
"Calls FUNC repeatedly, N times.
Warning: Never use i, n, func in FUNC body as nonlocal variables."
答案 0 :(得分:5)
Alice会注意不要在lambda
主体中使用非局部变量,意识到lambda
没有创建词法闭包。
在Emacs Lisp中,这个简单的策略实际上足以避免动态范围的大多数问题,因为在没有并发的情况下,本地let
- 动态变量的绑定大多等同于词法绑定。
换句话说,Emacs Lisp开发人员的“旧时代”(由于动态作用域的Emacs Lisp数量仍然存在而不是那么老)不会像这样编写lambda
。他们甚至都不想,因为Emacs Lisp不是一种函数式语言(并且仍然不是),因此循环和显式迭代通常比高阶函数更受欢迎。
关于你的具体例子,“旧时代”的爱丽丝会写两个嵌套循环。
答案 1 :(得分:4)
Emacs解决这个问题的方法是遵循一个非常严格的约定:Elisp程序员编写高阶函数(例如你的fp-repeat
)当光线照射它们时,它们会使用不寻常的变量名称意识到这个功能可以被其他人使用,当光线不起作用时,他们应该做每日祈祷(在Emacs教堂总是一个好主意)。
答案 2 :(得分:3)
除了lunaryorn和Stefan所说的话:
在您提供的特定示例中,传递给fp-repeat
的funarg实际上根本不需要变量i
。
也就是说,它不需要对i
AS A VARIABLE做任何事情。也就是说,它不需要使用i
作为特定的SYMBOL,其值将在特定时间或特定上下文(环境)中确定,当&调用函数的地方。
所有功能真正需要的是i
的值,该功能在定义的时间和地点。在这种特殊情况下使用变量是过度的 - 只需要它的值。
另一种解决问题的方法是 在函数定义中替换变量值 ,即在lambda
表达式中:
(defun my-insert-stuff ()
(interactive)
(dolist (i (list "1" "2" "3"))
(fp-repeat 10 `(lambda () (insert ',i)))
(insert "\n")))
这很好用。没有变量捕获的可能性,因为没有变量。
缺点是在编译时也没有函数:构造了LIST,其car
是lambda
等。然后在运行时评估该列表,将其解释为函数。 / p>
根据具体的使用案例,这可能是一种有用的方法。是的,这意味着你必须区分你真正需要使用变量的上下文(函数使用VARIABLE i
而不只是一个值)。