Common Lisp 中函数之间的循环依赖

Circular dependency between functions in Common Lisp

是否可以在 Common LISP 中定义两个相互调用的函数而不收到样式警告?解决该问题的最佳想法是将其中一个函数作为第二个函数的参数,但我认为这个想法不是很优雅。

当然可以。您只需要注意不要创建无限循环即可。所以这里同样适用于递归(你需要一个终止案例)。

顺便说一句,当函数调用未定义的函数时,SBCL 会发出警告。

pozdrowienia!

考虑到 defun 不需要出现在顶层这一事实(请参阅 Common Lisp the Language, 2nd Edition),您可以在没有样式警告的情况下以有点复杂的方式定义两个相互递归函数:

X3J13 voted in March 1989 (DEFINING-MACROS-NON-TOP-LEVEL) to clarify that, while defining forms normally appear at top level, it is meaningful to place them in non-top-level contexts; defun must define the function within the enclosing lexical environment, not within the null lexical environment.

一个例子是使用特殊运算符 labels (manual),它允许定义相互递归函数:

CL-USER> (labels ((f (x)
                    (cond ((> x 0) (g (1- x)))
                          ((= x 0) x)
                          (t (g (1+ x)))))
                  (g (y)
                    (if (= y 0)
                        0
                        (f y))))
           (defun f(x) (funcall #'f x))
           (defun g(x) (funcall #'g x)))
G
CL-USER> (f 3)
0
CL-USER> (g 3)
0

是的,当然是,而且不需要什么特殊的魔法。

只需定义一个,然后再定义另一个。如果您在 REPL 中执行此操作,或通过选择性地评估文件中的各个定义,或通过加载文件的源代码,那么您可能会在定义第一个函数时收到警告,直到定义第二个函数(因此您只有在你第一次定义第一个函数时才会得到这个):

cl-user> (defun foo (x)
           (if (null x)
               1
               (bar (rest x))))
; in: defun foo
;     (BAR (REST X))
; 
; caught style-warning:
;   undefined function: common-lisp-user::bar
; 
; compilation unit finished
;   Undefined function:
;     bar
;   caught 1 STYLE-WARNING condition
foo
cl-user> (defun bar (x)
           (foo x))
bar
cl-user> (foo '(1 2 3))
1
cl-user>

实际上,您可能只会在 SBCL 和 CMUCL 等纯编译器实现中收到这样的警告。没有理由为什么其他实现不应该发出警告:它们通常不会发出警告,因为这样的警告往往很烦人。在仅编译器的实现中,它们只是不容易避免,假设您希望在确实 缺少定义时看到警告。

如果只是将这两个函数放到一个文件中,然后编译它们:

cl-user> (compile-file "/tmp/x.lisp" :load t)
; Evaluation aborted on #<unknown-keyword-argument {100219F653}>.
cl-user> (load (compile-file "/tmp/x.lisp"))
; compiling file "/tmp/x.lisp" (written 21 APR 2020 04:35:27 PM):
; compiling (defun foo ...)
; compiling (defun bar ...)

; wrote /tmp/x.fasl
; compilation finished in 0:00:00.002
t
cl-user> (foo '(1 2 3 4))
1

您不会收到任何警告。

特别是在 SBCL(可能还有 CMUCL)中,您可以在使用 with-compilation-unit:

加载源文件时防止警告
cl-user> (with-compilation-unit ()
           (load "/tmp/x.lisp"))
t

这会将编译器的警告延迟到表单末尾,此时所有 none。我不认为这是可以依赖的(我认为它在其他实现中是无害的,但他们仍然可以警告)。

(请注意,以上每个摘录均来自新的 SBCL。)

Common Lisp 定义了编译单元,您甚至可以使用WITH-COMPILATION-UNIT手动指定编译单元的范围。

例如当你调用COMPILE-FILE时,整个文件代表一个编译单元:编译器通常将警告推迟到文件末尾,这意味着如果你正确定义了两个相互递归的函数,则不会警告一下。

另一种可能性是执行前向声明:

(declaim (ftype function f))
(defun g () (f))
(defun f () (g))

第一个declaim表示要考虑ffbound(该符号在函数命名空间中绑定),抑制该点的警告在编译期间使用。