将构造函数和选择器定义为 cons、car 和 cdr 是否仍然不可取?

问题描述

计算机程序的结构和解释具有以下footnote

另一种定义选择器和构造器的方法是

(define make-rat cons)
(define numer car)
(define denom cdr)

第一个定义将名称 make-rat 与表达式 cons 的值相关联,这是构造对的原始过程。因此,make-ratcons 是同一个原始构造函数的名称。

以这种方式定义选择器和构造器是有效的:而不是 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

别名 barfoo

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 nilinline),以防您担心性能。而在 Scheme 中,您可以做同样的事情,或者加载一个不同的文件,在您交付程序时创建别名而不是包装器。

我还认为,除非您在测试时可以识别实际瓶颈,否则这对于避免优化更可取。

相关问答

错误1:Request method ‘DELETE‘ not supported 错误还原:...
错误1:启动docker镜像时报错:Error response from daemon:...
错误1:private field ‘xxx‘ is never assigned 按Alt...
报错如下,通过源不能下载,最后警告pip需升级版本 Requirem...