关于 C-x C-e 作用域

(defvar qwe 3)

(setq qwe 1)

(defun my-print-qwe () “” (message “inner: qwe = %d” qwe))

(let ((qwe 2)) (message “let: qwe = %d” qwe) (my-print-qwe))

;; lexical-binding 一直都是 启用的

请问,当我将光标放置在第二行行末输入 C-x C-e 后,接着分别输入 C-x C-e执行后两条语句(完全忽视 defvar),为什么仍然输出 “inner: qwe = 2"?而如果一开始就把第一行注释掉,其余操作不变,就会是符合预期的 “inner: qwe = 1"?

defvar 会标记变量是 动态作用域 ,也就是和具体的调用环境有关。

你在第四行把它临时绑定为 2,那它暂时的值就是 2 了。

1 个赞

您好。

可是我并没有执行第一行啊?

我是直接从第二行开始执行的(把光标放在第二行行末,按下 C-x C-e),结果却是 inner 为 2。

然而当我把第一行注释掉,依旧从第二行执行,就没问题了(inner 就输出 1 了)。。。

也就是说你没有 C-x C-e 过第一行。

那在您的 Emacs 中,以下两段代码的求值结果是一样的吗(先对前一段求值)

(let ((lexical-binding t))
  ;;(defvar qwe 3)
  (setq qwe 1)
  (defun my-print-qwe () "" (message "inner: qwe = %d" qwe))
  (let ((qwe 2)) (message "let: qwe = %d" qwe) (my-print-qwe)))

(let ((lexical-binding t))
  (defvar qwe 3)
  (setq qwe 1)
  (defun my-print-qwe () "" (message "inner: qwe = %d" qwe))
  (let ((qwe 2)) (message "let: qwe = %d" qwe) (my-print-qwe)))

在我的 Emacs 中,前者会输出 2, 1,后者会输出 2, 2

我的结果和您的一样,前者 2, 1,后者 2, 2。

还有一件事,我是在默认的 “Scratch” buffer 求值的,不知道这是否是奇怪现象的罪魁祸首(不会是 configuration 导致的,因为运行环境为 emacs -Q)?

而且,即使是在 “Scratch” buffer 中运行,如果我用 eval-region 从第二行开始求值(将整行全部 mark起来),而非 C-x C-e,也会得到符合预期的结果(也就是 inner 为 1)。

这应该就是问题所在,由于 *scratch* buffer 中 lexical-bindingnil。即使您在该 buffer 的开头添加 ;;; lexical-binding: nil; ,该 buffer 仍然使用动态作用域。

您可以尝试在 *scratch* buffer 中执行 (setq lexical-binding t) 后,再观察注释 defvar 部分是否影响代码求值结果,我认为应该不会影响。

然而并不是😂。lexical-binding 就是 t 。

这个问题我先暂时搁置吧,因为我也不是熟练 emacs 用户。我测试这个例子就是为了检验自己对 defvar 的理解。现在看来我的理解没错(就是 defvar 会把变量标记为 special,lexical binding 不起作用)。

最后想问您一个问题,chatgpt 说

Emacs 会在执行 setq 或其他形式的变量赋值之前确保变量已经被定义,因此在执行 (setq qwe 1) 时,如果 qwe 尚未定义,Emacs 会先查找 defvar 的定义来初始化它。

我觉得这时在胡扯,对吗?

是的,zsbd

谢谢!zsbd

(let ((lexical-binding t)) ...) 应该是不管用的,我觉得是你当前 buffer 的 lexical-binding 已经改变了,才会得到符合预期的结果。

C-x C-e (eval-last-sexp) 的实现来看,(let ((lexical-binding t)) ...) 这种方式并没有真正改变 lexical-binding:

(defun elisp--eval-last-sexp (eval-last-sexp-arg-internal)
  "Evaluate sexp before point; print value in the echo area.
If EVAL-LAST-SEXP-ARG-INTERNAL is non-nil, print output into
current buffer.  If EVAL-LAST-SEXP-ARG-INTERNAL is `0', print
output with no limit on the length and level of lists, and
include additional formats for integers \(octal, hexadecimal, and
character)."
  (pcase-let*
      ((`(,insert-value ,no-truncate ,char-print-limit)
        (eval-expression-get-print-arguments eval-last-sexp-arg-internal)))
    ;; The expression might change to a different buffer, so record the
    ;; desired output stream now.
    (let ((output (if insert-value (current-buffer) t)))
      ;; Setup the lexical environment if lexical-binding is enabled.
      (elisp--eval-last-sexp-print-value
       (eval (macroexpand-all
              (eval-sexp-add-defvars
               (elisp--eval-defun-1 (macroexpand (elisp--preceding-sexp)))))
             lexical-binding)
       output no-truncate char-print-limit))))

放入测试代码,相当于:

(eval
 (macroexpand-all
  (elisp--eval-defun-1
   (macroexpand
    ;; ---- 8< ----
    '(let ((lexical-binding t))
       ;; (defvar qwe 3)
       (setq qwe 1)
       (defun my-print-qwe () "" (message "inner: qwe = %d" qwe))
       (let ((qwe 2)) (message "let: qwe = %d" qwe) (my-print-qwe)))
    ;; ---- >8 ----
    )))
 lexical-binding) ;; <--- 外面这个 `lexical-binding` 才是关键

应该用 emacs -Q 验证,而且执行过一次就该退出重启,以免测试环境受到污染。

1 个赞

应该是的。

不过题主(包括我)的测试环境 buffer 都绑定了 lexical-bindingt ,加或者不加最外面的那层 let 应该没有影响。

知道原因了,是 eval-sexp-add-defvars 这个函数导致的。 eval-last-sexp 会调用这个函数,而这个函数会执行这个 buffer 下的所有位于前面的 defvar 表达式。@include-yy @twlz0ne

3 个赞

有点意思,zsbd

1 个赞