;; -*- lexical-binding: t; -*-
(let ((url-cache-directory "~/org/url/cache")
(buffer nil))
(with-current-buffer
(url-retrieve
"https://cn.bing.com/"
(lambda (&rest args)
(message "%S %S" ;; => "t ~/.emacs.d/url/cache"
(eq (current-buffer) buffer)
url-cache-directory)))
(setq buffer (current-buffer))
url-cache-directory ;; => "~/org/url/cache"
))
问题:为什么 callback 里的 url-cache-directory 与 末尾的 url-cache-directory 绑定的值不同。
注1: url-retrieve-synchronously 无此问题。
注2: 在 emacs -Q 的 ielm 环境中首次执行上述代码片段时,报: Defining as dynamic an already lexical var: url-cache-directory. (require 'url-cache) 后错误不在,但绑定的值依旧不同。
我没有尝试复现,但是看起来这个问题应该是autoload造成的。
autoload的函数内部的动态绑定只有在加载它的时候才被创建,也就是说第一次加载的时候,url-cache-directory的词法绑定是先于动态绑定的。此时把它定义成动态变量就会报这个错误。在你执行(require 'url-cache)之后动态变量已经创建,此时词法绑定就可以正常生效了。
1 个赞
是的,注2 看起来是 defvar 准备创建 url-cache-directory 时却发现它已经被 lexical 绑定了。
但是不知道为什么,url 异步 retrieve 过后,它又被设置回默认值。也许,在 retrieving 时,执行环境已经脱离了 let, 即使该变量是 dynamic binding, 但因为执行流已出 let, 所以 retrieve 时,该变量的值已经恢复为默认。
(let ((url-cache-directory "~/org/url/cache")
(buffer nil)
(start (float-time))
(timeout 10)
(done nil))
(with-current-buffer
(url-retrieve
"https://cn.bing.com/"
(lambda (&rest args)
(message "%S %S" ;; => "t ~/org/url/cache"
(eq (current-buffer) buffer)
url-cache-directory)
(setq done t)))
(setq buffer (current-buffer))
(while (and (not done)
(< (- (float-time) start) timeout))
(sit-for 1))))
试了下加 waiting, 这样看起来可以临时改变 url-cache-directory 了。
wsug
2025 年6 月 18 日 01:13
5
想起我遇到的一个问题,回调函数中取不到url的值 ,但emacs -q却又可以,其它人也能取到,现在也不知为什么我这里不行
let 绑定对异步执行的支持可能存在欠缺,或者说,我们使用了 elisp 所 undefined 的操作。
我上面的代码虽然让 主页面 缓存到我指定的位置,但主页面中的一些图片元素还是被缓存到了默认位置。
我猜:因为整个请求是异步的, let 绑定退出时如果请求还没结束,那后续的请求将使用默认值。
我还在看有无解决方法。
加个闭包?
Origin
(let ((url-cache-directory "~/org/url/cache")
(buffer nil))
(with-current-buffer
(url-retrieve
"https://cn.bing.com/"
(lambda (&rest args)
(message "%S %S" ;; => "t ~/.emacs.d/url/cache"
(eq (current-buffer) buffer)
url-cache-directory)))
(setq buffer (current-buffer))
url-cache-directory ;; => "~/org/url/cache"
))
;;Result: "~/org/url/cache"
;;*Message*: t "d:/_D/msys64/home/26633/.emacs.d/url/cache"
New
(let ((url-cache-directory "~/org/url/cache")
(buffer nil))
(with-current-buffer
(url-retrieve
"https://cn.bing.com/"
(let ((dir url-cache-directory))
(lambda (&rest args)
(message "%S %S" ;; => "t ~/.emacs.d/url/cache"
(eq (current-buffer) buffer)
dir))))
(setq buffer (current-buffer))
url-cache-directory ;; => "~/org/url/cache"
))
;;Result: "~/org/url/cache"
;;*Message*: t "~/org/url/cache"
1 个赞
不行,这样没有解决我的问题。原谅我的表述有些 X-Y problem.
我的目的是将 url-cache 的存储路径临时地切换到我指定的目录下(一方面不想污染默认配置,另一方面我希望这些特定的 cache 没有 expired 时间)。我的根本目的是 cache 页面文本,只是因为不想自己写 cache, 想借 url-cache 减少工作量。
起初,我试着用 let 临时地改变 url-cache 的 custom 变量 url-cache-directory 来实现这一点,但随后我便遇到了本贴子的问题。
仔细想想,实际上,url-cache-directory 正和 defvar 定义的变量一样,是动态绑定的,所以,题干 callback 里的 url-cache-directory 实际已经不受词法范围约束了——这和 elisp 的定义是一致的,只是我预期 callback 里的 url-cache-directory 能受 let 约束。
因为它 (那个作为 callback 的 lambda) 看起来“就在 let 里头一样”。
1 个赞
我做了个实验,跟你的结果相同。
从打印顺序上看,url-retrieve里的lambda的执行是发生在你执行(setq buffer (current-buffer))之后的。
Doerthous:
let 绑定对异步执行的支持可能存在欠缺
参考Emacs Lisp RFM 12.10.1
Lexical binding is only available in the modern Emacs Lisp dialect.
(*Note Selecting Lisp Dialect::.) A lexically-bound variable has
“lexical scope”, meaning that any reference to the variable must be
located textually within the binding construct. Here is an example
(let ((x 1)) ; ‘x’ is lexically bound.
(+ x 3))
⇒ 4
(defun getx ()
x) ; ‘x’ is used free in this function.
(let ((x 1)) ; ‘x’ is lexically bound.
(getx))
error→ Symbol's value as variable is void: x
Here, the variable ‘x’ has no global value. When it is lexically bound
within a ‘let’ form, it can be used in the textual confines of that
‘let’ form. But it can not be used from within a ‘getx’ function
called from the ‘let’ form, since the function definition of ‘getx’
occurs outside the ‘let’ form itself.
这里的lambda如果没有被当作一个闭包处理,那么它里面的lexical binding应该是不生效的。所以我想应该传一个闭包进去,或者给它传参数进去。
1 个赞
预期而言, lambda 在 let 中,算 lexical. 但实际的执行效果反而像是:
;; 先执行完 let
(let ((url-cache-directory "~/org/url/cache")
(buffer nil))
...
(url-retrieve
"https://cn.bing.com/"
(lambda ...))
...
))
;; lambda 被异步执行
;; lambda 在上段代码中被求值为闭包
;; 捕获了 buffer 变量,但未捕获 url-cache-directory.
((lambda (&rest args)
(message "%S %S" ;; => "t ~/.emacs.d/url/cache"
(eq (current-buffer) buffer)
url-cache-directory)))
上述 lambda 被求值后的闭包应该如下:
ELISP> (let ((buffer nil)
(url-cache-directory "."))
(lambda () (list buffer url-cache-directory)))
#f(lambda () [(buffer nil)] (list buffer url-cache-directory))
;; 没捕获 url-cache-directory
顺便一提, let 可以算作 lambda 的语法糖, 下面的方法可以完全 lexical, 即使 bindings 中有被定义为 special-variable 的 symbol.
ELISP> ((lambda (buffer url-cache-directory)
(lambda () (list buffer url-cache-directory)))
nil ".")
#f(lambda () [(url-cache-directory ".") (buffer nil)]
(list buffer url-cache-directory))
ELISP>
不过,它不能完成我想要实现的效果,因为 url-retrieve 是异步的, lexical 绑定在这里无法发挥作用。
简单概括的话, elisp 的 let 还不完全等价 cl-lib 中的 lexical-let 吧。
请教个问题:
(let bindings &rest body) 的 body 里有一句 (url-retrieve url (lambda ...)).
这里的 lambda 算不算 located textually within the binding construct ?
还是说 lambda 必须作为 body list 的 element?
算的,但是这个问题的情况似乎比较特殊。
考虑这个简单的例子:(lexical-binding默认开启)
(let ((x 1))
(lambda () x))
求值得到#[nil (x) ((x . 1))],let的词法绑定生效。
但是如果前面加上
(defvar x 0)
(let ((x 1))
(lambda () x))
得到是#[nil (x) (t)]。x变成了动态绑定的,let的词法绑定不生效。
我想这是defvar的问题。defvar的文档里说:
If INITVALUE is provided, the ‘defvar’ form also declares the variable
as “special”, so that it is always dynamically bound even if
‘lexical-binding’ is t . If INITVALUE is missing, the form marks the
variable “special” locally (i.e., within the current
lexical scope, or the current file, if the form is at top-level),
and does nothing if ‘lexical-binding’ is nil.
那么defcustom是不是有这个问题呢?它的文档里说:
This macro uses defvar' as a subroutine, which also marks the variable as \"special\", so that **it is always dynamically bound** **even when lexical-binding’ is t**.
相关问题我看到有一个相关的帖子之前讨论过:
Reported as bug#64272 .
我尝试了:
;;; ~/.emacs -*- lexical-binding: t; -*-
(minibuffer-message "-*- %s -*-" lexical-binding)
启动时消息显示 nil:
[image]
也就是说 .emacs 中的代码都不是在词法环境下求值的?
但我看到很多人的 .emacs 都 (使用 file local variable 的方式) 开启了 lexical-binding, 请问这样做的目的是什么?
1 个赞
gynamics:
算的,
嗯嗯,了解了。这个问题是想细化下我目前对 let 范围的理解,试澄清是只要是 let 的子孙节点,还是必须是直接子节点才会受到词法约束。
gynamics:
但是这个问题的情况似乎比较特殊。
这个帖子的问题我大致有个理解轮廓了。
Reported as bug#64272 .
我尝试了:
;;; ~/.emacs -*- lexical-binding: t; -*-
(minibuffer-message "-*- %s -*-" lexical-binding)
启动时消息显示 nil:
[image]
也就是说 .emacs 中的代码都不是在词法环境下求值的?
但我看到很多人的 .emacs 都 (使用 file local variable 的方式) 开启了 lexical-binding, 请问这样做的目的是什么?
这贴子的讨论有点超出我的认知范围,我暂时按 defvar 理解 defcustom. 感觉, customize 接口和我当前以 Org Tangle 的方式管理 Emacs 配置的场景不太适配,所以暂时没研究。