aur*_*amo 53 lisp emacs scope elisp
我之前学过Clojure,非常喜欢这门语言.我也喜欢Emacs,并用Emacs Lisp攻击了一些简单的东西.有一件事使我无法在精神上做任何与Elisp有关的事情.这是动态范围的概念.我只是害怕它,因为它对我来说太陌生,闻起来像半全局变量.
因此,对于变量声明,我不知道哪些事情是安全的,哪些是危险的.根据我的理解,使用setq设置的变量属于动态范围(是吗?)让变量怎么样?在某个地方我读过,让你可以做一些简单的词法范围,但在其他地方我读过,让vars也是动态范围的.
我最担心的是我的代码(使用setq或let)意外地从我调用的平台或第三方代码中破坏了一些变量,或者在调用之后我的局部变量意外地搞砸了.我怎么能避免这个?
是否有一些简单的经验法则我可以遵循并确切地知道范围发生了什么,而不会被一些奇怪的,难以调试的方式咬住?
Rai*_*wig 45
这并不坏.
主要问题可能出现在函数中的"自由变量"中.
(defun foo (a)
(* a b))
Run Code Online (Sandbox Code Playgroud)
在上面的函数a是一个局部变量.b是一个自由变量.在像Emacs Lisp这样具有动态绑定的系统中,b将在运行时查找.现在有三种情况:
b 未定义 - >错误b 是当前动态范围中某些函数调用绑定的局部变量 - >取该值b 是一个全局变量 - >取这个值问题可能是:
在带有编译器的Lisp中,编译上述函数可能会生成一个警告,指出有一个自由变量.通常,Common Lisp编译器会这样做.解释器不会提供该警告,只会在运行时看到效果.
建议:
*foo-var*别写了
(defun foo (a b)
...
(setq c (* a b)) ; where c is a free variable
...)
Run Code Online (Sandbox Code Playgroud)
写:
(defun foo (a b)
...
(let ((c (* a b)))
...)
...)
Run Code Online (Sandbox Code Playgroud)
绑定您要使用的所有变量,并确保它们不会绑定到其他位置.
基本上就是这样.
由于GNU Emacs版本24的词法绑定在其Emacs Lisp中得到支持.请参阅:Lexical Binding,GNU Emacs Lisp参考手册.
Jér*_*dix 13
是否有一些简单的经验法则我可以遵循并确切地知道范围发生了什么,而不会被一些奇怪的,难以调试的方式咬住?
阅读Emacs Lisp参考,你会有很多这样的细节:
这是一个例子:
(defun foo () (setq tata "foo"))
(defun bar (tata) (setq tata "bar"))
(foo)
(message tata)
===> "foo"
(bar tata)
(message tata)
===> "foo"
Run Code Online (Sandbox Code Playgroud)
dan*_*lei 13
除了Gilles的最后一段回答之外,RMS还支持在可扩展系统中支持动态范围:
一些语言设计者认为应该避免动态绑定,而应该使用显式参数传递.想象一下,函数A绑定变量FOO,并调用函数B,它调用函数C,C使用FOO的值.据说A应该将值作为参数传递给B,它应该将它作为参数传递给C.
但是,这不能在可扩展系统中完成,因为系统的作者无法知道所有参数是什么.想象一下,函数A和C是用户扩展的一部分,而B是标准系统的一部分.变量FOO在标准系统中不存在; 它是扩展的一部分.要使用显式参数传递,需要向B添加一个新参数,这意味着重写B和调用B的所有内容.在最常见的情况下,B是编辑器命令调度程序循环,从很多地方调用.
更糟糕的是,C还必须传递一个额外的参数.B没有按名称引用C(写B时C不存在).它可能在命令调度表中找到指向C的指针.这意味着有时调用C的同一调用同样可以调用任何编辑器命令定义.因此,必须重写所有编辑命令以接受并忽略其他参数.到现在为止,原来的系统都没有留下!
就个人而言,我认为如果Emacs-Lisp存在问题,那么它本身并不是动态范围,但它是默认的,并且不可能在不诉诸扩展的情况下实现词法范围.在CL中,可以使用动态和词法作用域,并且 - 除了顶级(由几个deflex实现处理)和全局声明的特殊变量 - 默认为词法作用域.在Clojure中,您也可以使用词法和动态范围.
再次引用RMS:
动态范围不一定是唯一提供的范围规则,只有它可用才有用.
Giz*_*wai 11
正如Peter Ajtai指出的那样:
从emacs-24.1开始,您可以通过put在每个文件的基础上启用词法作用域
;; -*- lexical-binding: t -*-
Run Code Online (Sandbox Code Playgroud)
在你的elisp文件之上.
Vat*_*ine 10
首先,elisp具有单独的变量和函数绑定,因此动态范围的一些缺陷是无关紧要的.
其次,您仍然可以使用setq设置变量,但是值集不会在它完成的动态范围退出后继续存在.从根本上说,这与词法范围不同,不同之处在于动态范围设置中的setq您调用的函数可能会影响函数调用后看到的值.
有lexical-let一个宏(基本上)模仿词汇绑定(我相信它是通过行走身体并将所有出现的词法变量更改为一个gensymmed名称,最终是uninterning符号),如果你绝对需要的话.
我会说"正常编写代码".有时候,elisp的动态性质会咬你,但我发现在实践中这种情况很少令人惊讶.
这是我对setq和动态绑定变量(最近在附近的临时缓冲区中评估)所说的一个例子:
(let ((a nil))
(list (let ((a nil))
(setq a 'value)
a)
a))
(value nil)
Run Code Online (Sandbox Code Playgroud)
这里写的所有内容都是值得的.我想补充一点:了解Common Lisp - 如果没有别的,请阅读它.与其他书籍一样,CLTL2很好地呈现了词汇和动态绑定.Common Lisp将它们集成在一种语言中.
如果你在接触到Common Lisp之后"得到它",那么对于Emacs Lisp来说,事情会更加清晰.Emacs 24默认使用词法范围比默认版本更大,但Common Lisp的方法仍然更清晰,更清晰(恕我直言).最后,这是肯定的是动态范围是重要的Emacs Lisp,对于RMS和其他人强调的理由的情况下.
所以我的建议是要了解Common Lisp如何处理这个问题.如果这是Lisp的主要心理模型,那就试着忘记Scheme吧 - 它将限制你,而不是帮助你理解Emacs Lisp中的范围,funargs等.与Common Lisp一样,Emacs Lisp"肮脏而低调"; 它不是Scheme.
当一段代码用于与其定义的范围不同的范围时,动态和词法范围具有不同的行为.在实践中,有两种模式涵盖了最麻烦的情况:
函数隐藏全局变量,然后调用另一个使用该全局变量的函数.
(defvar x 3)
(defun foo ()
x)
(defun bar (x)
(+ (foo) x))
(bar 0) ? 0
Run Code Online (Sandbox Code Playgroud)
这在Emacs中并不常见,因为局部变量往往具有短名称(通常是单个单词),而全局变量往往具有长名称(通常以前缀为单位packagename-).许多标准函数的名称很容易被用作局部变量,例如list和point,但函数和变量存在于单独的名称空间中,本地函数不经常使用.
函数在一个词汇上下文中定义,并在此词汇上下文之外使用,因为它被传递给更高阶的函数.
(let ((cl-y 10))
(mapcar* (lambda (elt) (* cl-y elt)) '(1 2 3)))
? (10 20 30)
(let ((cl-x 10))
(mapcar* (lambda (elt) (* cl-x elt)) '(1 2 3)))
? (wrong-type-argument number-or-marker-p (1 2 3))
Run Code Online (Sandbox Code Playgroud)
该错误是由于(从包中)使用cl-x变量名称.请注意,即使对于高阶函数中的局部变量,该包也使用前缀作为前缀.这在实践中运行得相当好,只要您注意不要使用相同的变量作为全局名称和本地名称,并且您不需要编写递归的高阶函数.mapcar*clclcl-
PS Emacs Lisp的年龄并不是它动态范围的唯一原因.没错,在那些日子里,lisps倾向于动态范围 - Scheme和Common Lisp还没有真正开始.但动态范围界定也是一种旨在动态扩展系统的语言的资产:它可以让您无需任何特别的努力即可连接到更多地方.拥有强大的力量可以让你自己上吊绳索:你不小心挂进了一个你不知道的地方.