问题描述
计算机程序的结构和解释具有以下footnote:
另一种定义选择器和构造器的方法是
(define make-rat cons)
(define numer car)
(define denom cdr)
第一个定义将名称 make-rat
与表达式 cons
的值相关联,这是构造对的原始过程。因此,make-rat
和 cons
是同一个原始构造函数的名称。
以这种方式定义选择器和构造器是有效的:而不是 make-rat
调用 cons
,make-rat
is {{1 }},所以当调用 cons
时,只会调用一个过程,而不是两个过程。另一方面,这样做会破坏跟踪过程调用或在过程调用上设置断点的调试辅助:您可能希望看到 make-rat
被调用,但您当然不想看到对 {{1} 的每次调用}.
这个建议仍然适用吗?例如,现代调试辅助工具是否仍然以这种方式失败?
解决方法
在 Common Lisp 中也可以做到这一点。我们可以设置一个符号的符号功能。
(setf (symbol-function 'numer)
(function car))
另一种方法是定义这些函数:
(defun numer (rat)
(car rat))
现在将有调用这些额外函数的开销。这可以在开发和调试过程中提供帮助。
在 Common Lisp 中,可以给编译器一个提示,它可以内联函数:
(declaim (inline numer))
然后在用于生产或交付的优化编译代码中,可以内联函数:函数调用开销将不存在,但调用将不再可见。
,他们经常会这样做。例如,想象一些调试器试图以有用的方式打印回溯。它将想要在位于回溯中的过程对象及其名称之间进行映射。该地图要么指向“错误”的名称,要么指向所有名称,然后您必须知道您实际使用的是哪个名称。
这是 Racket 中的一个示例:
> (object-name cons)
'cons
> (define make-thingy cons)
> (object-name make-thingy)
'cons
,
这个建议仍然适用吗?例如,现代调试辅助工具是否仍然以这种方式失败?
普通 Lisp
在 Common Lisp 中,跟踪代码是通过提供我们要跟踪的函数的名称来制作的。这意味着跟踪可以区分不同的名称,即使它们指的是同一个对象。例如,在 SBCL 中(这与其他实现无关),我们可以这样做。
定义foo
USER> (defun foo () 1)
FOO
别名 bar
到 foo
:
USER> (setf (symbol-function 'bar) #'foo)
#<FUNCTION FOO>
注意函数本身的名称,foo
。
调用 bar
有效:
USER> (bar)
1
跟踪bar
:
USER> (trace bar)
(BAR)
USER> (bar)
0: (PARROT.USER::BAR)
0: BAR returned 1
1
注意 foo
没有被追踪:
USER> (foo)
1
我认为它有效是因为 TRACE 使 bar
绑定到其先前绑定的包装器(即 #'foo
),因此调用 bar
会在 {{1} 周围执行一些跟踪代码},但不会对 foo
本身做同样的事情。
但是请注意,在尝试跟踪 foo
时有一些奇怪的行为:
foo
奇怪的是,USER> (trace foo)
WARNING: FOO is already TRACE'd,untracing it first.
(FOO)
USER> (foo)
0: (PARROT.USER::FOO)
0: FOO returned 1
1
不再被跟踪(好吧,它说它首先取消了它的跟踪,这可能是未跟踪的 bar
):
bar
此外,USER> (bar)
1
可以封装或不封装被追踪的函数:
trace
将 :ENCAPSULATE {:DEFAULT | T | NIL}
If T,the default,tracing is done via encapsulation (redefining the
function name) rather than by modifying the function. :DEFAULT is
not the default,but means to use encapsulation for interpreted
functions and funcallable instances,breakpoints otherwise. When
encapsulation is used,forms are *not* evaluated in the function's
lexical environment,but SB-DEBUG:ARG can still be used.
与 NIL 一起使用时,跟踪 encapsulate
也会跟踪 bar
:
foo
取消追踪 USER> (untrace)
T
USER> (bar)
1
USER> (foo)
1
USER> (trace bar :encapsulate nil)
(BAR)
USER> (bar)
0: (PARROT.USER::FOO)
0: BAR returned 1
1
USER> (foo)
0: (PARROT.USER::FOO)
0: BAR returned 1
1
会使 foo
无法追踪:
bar
鸡肉计划
例如,在Chicken Scheme中,跟踪是an extension,它依赖于advice,而{{3}}又依赖于改变过程的内部机制(在过程中提到了转发表),这意味着过程本身(而不是它的名字)正在进行跟踪。
这看起来很像上面的 USER> (trace)
(BAR)
USER> (untrace foo)
T
USER> (bar)
1
案例。
结论
我不会依赖这种别名来工作,因为这看起来有点脆弱,而且不便携。 Common Lisp 允许您以类似于函数的方式定义访问器,但在编译期间进行扩展(使用 :encapsulate nil
或 inline
),以防您担心性能。而在 Scheme 中,您可以做同样的事情,或者加载一个不同的文件,在您交付程序时创建别名而不是包装器。
我还认为,除非您在测试时可以识别实际瓶颈,否则这对于避免优化更可取。