首先,永远不要低估风格的重要性。
我们编写代码不仅是为了让计算机运行,更重要的是,为了人们阅读。
使代码对人们来说可读和易于理解是软件开发的一个非常重要的方面。
第二,是的,有(setf fdefinition)和defun有很大的区别。
“小的”区别在于defun 还可以设置函数名称的doc string(实际上,取决于您的实现方式,它也可以使用 lambda 来实现),并创建一个名为 block (在下面的宏扩展中看到)如果你愿意,你必须自己创建。
最大的区别在于编译器“知道”defun 并将对其进行适当的处理。
例如,如果您的文件是
(defun foo (x)
(+ (* x x) x 1))
(defun bar (x)
(+ (foo 1 2 x) x))
那么编译器可能会警告您在 bar 中调用 foo 时使用了错误数量的参数:
警告:在第 3..4 行的 BAR 中:使用 3 个参数调用 FOO,但它需要 1 个
争论。
[FOO 在第 1..2 行中定义]
如果将defun foo 替换为(setf (fdefinition 'foo) (lambda ...)),编译器不太可能会小心处理它。此外,您可能会收到类似
的警告
以下函数已使用但未定义:
喂
您可能想通过macroexpanding 它检查defun 在您的实现中做了什么:
(macroexpand-1 '(defun foo (x) "doc" (print x)))
CLISP 将其扩展为
(LET NIL (SYSTEM::REMOVE-OLD-DEFINITIONS 'FOO)
(SYSTEM::EVAL-WHEN-COMPILE
(SYSTEM::C-DEFUN 'FOO (SYSTEM::LAMBDA-LIST-TO-SIGNATURE '(X))))
(SYSTEM::%PUTD 'FOO
(FUNCTION FOO
(LAMBDA (X) "doc" (DECLARE (SYSTEM::IN-DEFUN FOO)) (BLOCK FOO (PRINT X)))))
(EVAL-WHEN (EVAL)
(SYSTEM::%PUT 'FOO 'SYSTEM::DEFINITION
(CONS '(DEFUN FOO (X) "doc" (PRINT X)) (THE-ENVIRONMENT))))
'FOO)
SBCL 会:
(PROGN
(EVAL-WHEN (:COMPILE-TOPLEVEL) (SB-C:%COMPILER-DEFUN 'FOO NIL T))
(SB-IMPL::%DEFUN 'FOO
(SB-INT:NAMED-LAMBDA FOO
(X)
"doc"
(BLOCK FOO (PRINT X)))
(SB-C:SOURCE-LOCATION)))
这里的重点是defun 有很多“幕后”,这是有原因的。另一方面,setf fdefinition 更多的是“所见即所得”,即不涉及魔法。
这并不意味着setf fdefinition 在现代 lisp 代码库中没有位置。例如,您可以使用它来实现“穷人的trace”(未测试):
(defun trace (symbol)
(setf (get symbol 'old-def) (fdefinition symbol)
(fdefinition symbol)
(lambda (&rest args)
(print (cons symbol args))
(apply (get symbol 'old-def) args))))
(defun untrace (symbol)
(setf (fdefinition symbol) (get symbol 'old-def))
(remprop symbol 'odd-def))