【问题标题】:dealing with a Emacs Lisp dynamic scope pitfall in old days处理过去的 Emacs Lisp 动态范围陷阱
【发布时间】:2013-08-15 09:36:43
【问题描述】:

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

假设 Alice 编写了一个命令 my-insert-stuff,它依赖于在 fp.el 中定义的 fp-repeat 函数(我们假设这是一个为 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")))

鲍勃fp.el的部分内容:

(defun fp-repeat (n func)
  "Calls FUNC repeatedly, N times."
  (dotimes (i n)
    (funcall func)))

Alice 很快发现她的命令并没有像她预期的那样工作。那是因为 Alice 对 i 的使用和 Bob 对 i 的使用发生冲突。在过去,Alice 或/和 Bob 可以做些什么来防止这种碰撞的发生?

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

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

【问题讨论】:

    标签: emacs elisp dynamic-scope


    【解决方案1】:

    Alice 会注意不要在 lambda 主体中使用非局部变量,因为知道 lambda 不会创建词法闭包。

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

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

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

    【讨论】:

    • 高阶函数 mapcar 一直被大量使用(在较小程度上,mapc 和 mapconcat)。 CL 包添加了许多非常有用的高阶函数:mapcan、mapcon、maplist、some、every、notany、notevery、reduce、remove-if、remove-if-not 等。拒绝使用将是非常有限的函数式编程。
    • @to_the_crux 哦,亲爱的,请阅读这个问题,并将我的答案放到它的上下文中。我确实拒绝函数式编程。我只是陈述了一个事实,即很多 Emacs Lisp 代码更喜欢显式迭代而不是高阶函数。当然,正如您所观察到的,Emacs 具有这样的功能,但是这些(仍然?)不经常与 lambda 一起使用,甚至更不经常与闭包一起使用,这正是因为 Emacs 很长时间没有闭包。直到今天,Emacs Lisp 还不是一种函数式语言。充其量,它是一种包含一些 FP 技术的命令式语言。
    【解决方案2】:

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

    【讨论】:

      【解决方案3】:

      除了lunaryorn和Stefan所说的:

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

      也就是说,它不需要对i 作为变量做任何事情。也就是说,它不需要将i 用作特定的SYMBOL,其值将在特定时间或特定上下文(环境)中确定,何时何地调用函数。

      该函数真正需要的是i 的值,该函数在何时何地被定义。在这种特殊情况下使用变量是多余的——只需要它的值。

      所以另一种穿针引线的方法是将变量的值代入函数的定义中,即在lambda表达式中: p>

       (defun my-insert-stuff ()
         (interactive)
         (dolist (i (list "1" "2" "3"))
           (fp-repeat 10 `(lambda () (insert ',i)))
           (insert "\n")))
      

      这很好用。没有变量捕获的可能性,因为没有变量

      缺点是在编译时也没有函数:构造了一个 LIST,其 carlambda 等等。然后在运行时评估该列表,将其解释为一个函数。

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

      【讨论】:

      • 感谢您和@Stefan 的回答。我一直想知道如何避免动态范围的 elisp 中的名称冲突,官方文档中关于该主题的信息似乎很少。
      • 不是 Elisp 特有的。一般来说,你可以通过谷歌搜索 wrt Lisp 找到很多关于它的信息。 Common Lisp 官方文档也很好地描述了它。在过去,它被称为“funarg”问题(实际上,有向下和向上的两种问题),所以“funarg”在这里也可以是一个有用的搜索词。
      猜你喜欢
      • 1970-01-01
      • 2013-04-13
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2013-02-26
      相关资源
      最近更新 更多