用允许绑定的变量穿透“set-process-sentinel”层次结构



我从来没有能够想出一种方法来穿透set-process-sentinel层次结构,在函数开始时定义了允许绑定的变量——只有缓冲区局部或全局变量可以穿透它。左界变量可以到达第一个start-process,但这是它们可以穿透而不会因未被识别而被拒绝的程度——在函数开始处定义的左界变量似乎无法穿透以(lambda (p e) . . .开始的部分。有人能想到一种方法来做到这一点吗? 包括穿透嵌套的哨兵,如下面的例子所示?

(set-process-sentinel 
  (start-process
    "my-process-name-one"
     "*OUTPUT-BUFFER*"
    "/path/to/executable"
    "argument-one"
    "argument-two"
    "argument-three")
  (lambda (p e) (when (= 0 (process-exit-status p))
    (set-process-sentinel 
      (start-process
        "my-process-name-two"
        nil ;; example of not using an output buffer
        "/path/to/executable"
        "argument-one"
        "argument-two"
        "argument-three")
      (lambda (p e) (when (= 0 (process-exit-status p))
        (set-process-sentinel 
          (start-process . . . ))))))))

问题是Emacs Lisp变量绑定默认情况下是动态的。也就是说,当求值一个函数时,查找绑定变量的不是在定义函数的环境中,而是在调用函数的环境中。

Emacs 24或更高版本本身支持词法绑定(即,函数看到围绕函数定义绑定的变量),但由于它改变了现有代码的语义,因此需要显式启用它。通常,这是通过在.el文件的第一行添加一个文件局部变量设置来完成的:
;; -*- lexical-binding: t; -*-

另一种选择是使用cl库中的lexical-let。这也适用于早期的Emacs版本。注意,通过这种方式,您可以显式指定哪些变量应该具有词法绑定,因此像(lexical-let ((foo foo)) ...)这样的代码并不少见——foo是一个需要"结转"到函数中的现有变量。

下面是一个使用动态绑定的例子:

(defun example-dynamic-fn ()
"Doc-string"
(interactive)
  (let ((test-variable "Hello-world!"))
    (set-process-sentinel
      (start-process "process-one" "*one*" "echo" test-variable)
      `(lambda (p e) (when (= 0 (process-exit-status p))
        (set-process-sentinel
          (start-process "process-two" "*two*" "echo" ,test-variable)
          '(lambda (p e) (when (= 0 (process-exit-status p))
            (start-process "process-three" "*three*" "echo" ,test-variable)
            (set-process-sentinel
              (start-process "process-four" "*four*" "echo" ,test-variable)
              '(lambda (p e) (when (= 0 (process-exit-status p))
                (set-process-sentinel
                  (start-process "process-five" "*five*" "echo" ,test-variable)
                  '(lambda (p e) (when (= 0 (process-exit-status p))
                    (message "test-variable:  %s" ,test-variable)))))))))))))))

好的,我想我现在明白了。上面的链接提供了一个很好的例子;这是另一个,以防其他人有这个困难:

;;; ensure VAR1 has no binding
(makunbound 'VAR1)
;;;
(defun f1 (&optional VAR1)
  (interactive)
  (unless VAR1
    (set 'VAR1 "variable1"))
  (pop-to-buffer "*test*")
;  (lexical-let ( (VAR1 VAR1) ) ;;;
    (set-process-sentinel
     (start-process-shell-command "test"
                  "*test*"
                  (concat "echo " VAR1))
     (lambda (process event)
       (condition-case err
       (when (string-match-p "finished" event)
         (f2 VAR1))
     (error
      (princ
       (format "Sentinel error: %s" err))))))
;    ) ;;;
  )
;;;
(defun f2 (&optional VAR2)
   (interactive)
  (unless VAR2
    (set 'VAR2 "VARIABLE2"))
  (print VAR2))

加载所有内容(将(f1)中的行注释掉)并运行(f1)。在发生错误之前,将VAR1的值传递给(f2) 。错误(void-variable VAR1)似乎来自(set-process sentinel PROCESS SENTINEL)的作用域环境;这里没有定义VAR1,尽管它仍然在SENTINEL ((lambda))函数的作用域中。

同样,当变量仅对函数具有局部作用域时,如上所述使用(set )也不是最佳实践。

如果我们取消用;标记的行注释,那么一切都像预期的那样工作。令人高兴的是,我们可以将值传递给另一个函数,这可以防止长时间的(set-process sentinel )构建。如果需要,它还允许我们生成带有附加子过程的过程。

我的一个错误是将SENTINEL命名为离散函数,而不是将其保留在(lexical-let )函数中。虽然lexical-binding: t;方法很有吸引力,但它往往会破坏依赖于标准(let )的工作代码。

最新更新