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
相关产品推荐
相关产品推荐

