我从来没有能够想出一种方法来穿透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 )
的工作代码。