在过去处理Emacs Lisp动态范围陷阱

时间:2013-08-15 09:36:43

标签: emacs elisp dynamic-scope

在过去,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."

3 个答案:

答案 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,其carlambda等。然后在运行时评估该列表,将其解释为函数。 / p>

根据具体的使用案例,这可能是一种有用的方法。是的,这意味着你必须区分你真正需要使用变量的上下文(函数使用VARIABLE i而不只是一个值)。