4

在过去,Emacs 不支持词法作用域。我想知道当时人们如何处理动态范围的特定陷阱。

假设 Alice 编写了一个my-insert-stuff依赖于其中fp-repeat定义的函数的命令fp.el(我们假设这是一个为 Bob 编写的函数式编程提供大量函数的库),并且假设fp-repeat是为了多次重复调用一个函数。

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 可以做些什么来防止这种碰撞的发生?

也许 Bob 可以将文档字符串更改为

"Calls FUNC repeatedly, N times.
Warning: Never use i, n, func in FUNC body as nonlocal variables."
4

3 回答 3

5

Alice 会注意不要在正文中使用非局部变量lambda,因为意识到这lambda不会创建词法闭包。

在 Emacs Lisp 中,这个简单的策略实际上足以避免大多数动态范围问题,因为在没有并发的情况下,let动态变量的本地绑定大多等同于词法绑定。

换句话说,“旧时代”的 Emacs Lisp 开发人员(考虑到动态范围的 Emacs Lisp 仍然存在的数量并不那么老)不会写出lambda这样的东西。他们甚至不想这样做,因为 Emacs Lisp 不是函数式语言(现在仍然不是),因此循环和显式迭代通常比高阶函数更受欢迎。

关于您的具体示例,“过去”的 Alice 只会编写两个嵌套循环。

于 2013-08-15T10:40:28.523 回答
5

Emacs 处理这个问题的方式是遵循一个非常严格的约定:编写高阶函数(例如 your fp-repeat)的 Elisp 程序员被期望使用不寻常的变量名,因为他们意识到该函数可以被其他人,当光线不起作用时,他们应该做他们的日常祈祷(在 Emacs 教堂总是一个好主意)。

于 2013-08-15T13:35:09.493 回答
3

除了lunaryorn和Stefan所说的:

在您给出的特定示例中,传递给的 funargfp-repeat实际上根本不需要变量i

也就是说,它不需要对iAS 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等。然后在运行时评估该列表,将其解释为一个函数。

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

于 2013-08-15T14:35:48.023 回答