微信公众号搜"智元新知"关注
微信扫一扫可直接关注哦!

我如何判断一个elisp 表达式是否是纯的和常量?

如何解决我如何判断一个elisp 表达式是否是纯的和常量?

在某些时候,Emacs 添加pure 符号属性,指示已知哪些函数是纯函数(参见 here)。有没有办法使用这个属性来确定整个表达式是否是常量和无副作用的?例如,很容易确定 (car '(1 2 3))一个常量表达式,因为 car一个函数(即 (get 'car 'pure) 返回 t)而 '(1 2 3)一个带引号的形式。但是,还有更复杂的表达式,例如涉及特殊形式,它们仍然是不变的:

(let ((a (+ 1 2))
      (b (- 5 5)))
  (if (> b 0)
      a
    (- a)))

值得注意的是,letif 都没有被标记为纯,但这仍然是一个计算为 -3 的常量表达式。

有什么办法可以让我用任意表达式来判断它是否是常量?我知道有 unsafep,它似乎实现了必要的表达式树遍历,但它评估的标准与“常量”不同,因此我不能直接使用它。

  • 可以假设标准 Elisp 函数、宏或特殊形式(carletif 等)的任何定义都没有被修改
  • 立>
  • 就我而言,“常量”是使用 equal 定义的。因此,在每次调用时返回具有相同内容的新列表的函数将被视为常量。
  • 我知道有些常量表达式涉及不纯函数,例如(nreverse '(1 2 3))。我不介意算法是否遗漏了这样的表达式。

以防万一,我想这样做的原因是我正在实现一个 elisp 宏,其中出现在特定上下文中的常量表达式在语法上是有效的,但它们的返回值将被忽略,使它们毫无意义,并且可能程序员误解的结果。因此,如果宏在此上下文中看到常量表达式,我想发出警告。

解决方法

有什么办法可以让我用任意的表达式来判断它是否是常量?

没有


首先,每当 lisp 解释器看到一个符号时,如果它以前从未见过,它就会被驻留在 obarray(对象数组)中。所以严格意义上来说,大多数函数不可能是无副作用的。

此外,根据定义,某些操作具有副作用。例如,IO 在从 stdin 读取或写入 stdout/stderr 时会产生副作用。

因此我们需要将 side-effect-free 的定义限制为不改变外部状态。例如,以下内容不得为 side-effect-free

;; If a function modifies one of its input argument
(defvar foo nil)
(funcall (lambda () (setq foo t)))

;; If a function modifies a free variable. Here `free variable` means any non-constant ;; symbol from the enclosing environment
(funcall (lambda (x) (setq x t)) foo)
(funcall (lambda (x) (makunbound x)) 'foo)

;; If a function initialize a variable in the enclosing environment
(funcall (lambda (x) (defvar bar nil)))

其次,pure 函数必须是 side-effect-free 并且在给定相同输入的情况下返回相同的输出。

constant 在此上下文中并不意味着符号具有不变的值。这意味着在编译时可以知道 sexp 的值。这与 referential transparency 相关,但不相等。如果 sexpconstant,字节编译器可以简单地将 sexp 替换为其值。

也就是说,(declare (pure t)) 告诉编译器,如果此函数的所有输入参数都是常量(它们在编译时已知),则可以在编译时计算输出。


判断一个函数是否为side-effect-free,只需要检查符号的符号属性side-effect-freepure。或者你可以使用:

(defun side-effect-free-p (function)
  (and (symbolp function)
       (or (function-get function 'pure)
           (funciton-get function 'side-effect-free))))

同样,

(defun pure-p (function)
  (and (symbolp fucntion)
       (function-get function 'pure)))

绝大多数函数既不是side-effect-free也不是pure,但在特定调用中它们可以是side-effect-freepure。例如,

;; `let` - pure and side-effect free
(let ((foo nil))
  nil)

;; `let` - neither pure nor side-effect free
(defvar bar t)
(let ((foo (setq bar nil))) ; modified outside state,bar
  nil)

;; `let` - not pure but side-effect free
(let ((foo (current-time))) ; the first argument to let,not constant
  foo)
(let ()           ; empty local binding specification
  (current-time)) ; the second argument to let,it's not constant

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。