You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Common Lisp中#'callee与'callee的差异、调用原理及最佳实践咨询

先看你的代码和运行输出

首先把代码整理成可读性更好的格式:

(defun caller (f) 
  (format t "caller: f: ~a~%" f) 
  (funcall f 10))

(defun callee (x) 
  (format t "callee: x: ~a~%" x))

(caller #'callee)
(caller 'callee)

运行输出(整理后):

$ clisp foo.lisp
caller: f: #<FUNCTION CALLEE (X) (DECLARE (IN-DEFUN CALLEE)) (BLOCK CALLEE (FORMAT T callee: x: ~a~% X))>
callee: x: 10
caller: f: CALLEE
callee: x: 10

接下来逐个解答你的问题:


1. #'callee与'callee语法的区别是什么?

这俩本质上是完全不同的东西:

  • #'callee是(function callee)的简写,直接返回callee这个函数的实体对象。你看到输出里的#<FUNCTION ...>就是这个函数对象的打印形式,它是真正能执行代码的实体。
  • 'callee是(quote callee)的简写,返回的是名为CALLEE的符号对象——这个符号就像一个“名字标签”,它的函数槽(symbol-function)里存着对应的函数对象,但符号本身不是函数。输出里的CALLEE就是这个符号的打印形式。

打个比方:#'callee是你直接拿到了一台能干活的机器,而'callee是你拿到了写着“机器名字”的门牌,门牌指向那台机器,但门牌本身不能干活。


2. 为何funcall能成功调用两种形式传入的callee?

这是Common Lisp给funcall(还有apply)设计的一个“便利规则”:
当你传给funcall的第一个参数不是函数对象时,它会自动尝试把这个参数当作符号,去读取该符号的函数槽(也就是调用(symbol-function 参数)),取出里面的函数对象再执行。

所以当你传'callee时,funcall偷偷做了一步转换:把符号CALLEE转换成它对应的函数对象,然后再调用,所以结果和传#'callee一致。


3. 两种语法的优缺点及最佳实践是什么?

咱们分点说清楚:

  • #'callee的优缺点:

    • 优点:完全明确,没有歧义。比如如果某个符号同时被定义成变量和函数(虽然不推荐这么做),#'只会取函数对象,不会搞错;另外编译器可以直接识别这是函数引用,能做更精准的优化,避免隐式转换的开销。
    • 缺点:写法比'callee多了个#,稍微繁琐一点。
  • 'callee的优缺点:

    • 优点:写法简洁,少敲一个字符,适合快速写脚本。
    • 缺点:隐患很多——如果符号的函数槽被修改(比如用fset绑定了其他函数),或者符号有变量绑定,可能会触发意想不到的错误;另外在一些严格的编译模式下(比如SBCL的safety 0优化),可能会跳过隐式转换,直接报错。
  • 最佳实践:
    强烈推荐用#'callee(或者完整的(function callee)),尤其是在正式项目里。遵循“显式优于隐式”的原则,能让你的代码更清晰,避免绝大多数潜在的bug。只有在非常简单的一次性脚本、明确没有符号歧义的场景下,才可以偶尔用'callee偷懒。


内容的提问来源于stack exchange,提问作者Lone Learner

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.28 07:27:23