将构造函数和选择器定义为 cons、car 和 cdr 是否仍然不可取?
Is it still inadvisable to define constructors and selectors as cons, car, and cdr?
计算机程序的结构和解释有以下footnote:
Another way to define the selectors and constructor is
(define make-rat cons)
(define numer car)
(define denom cdr)
The first definition associates the name make-rat
with the value of the expression cons
, which is the primitive procedure that constructs pairs. Thus make-rat
and cons
are names for the same primitive constructor.
Defining selectors and constructors in this way is efficient: Instead of make-rat
calling cons
, make-rat
is cons
, so there is only one procedure called, not two, when make-rat
is called. On the other hand, doing this defeats debugging aids that trace procedure calls or put breakpoints on procedure calls: You may want to watch make-rat
being called, but you certainly don't want to watch every call to cons
.
这个建议还适用吗?比如,现代的调试辅助工具还这样被打败吗?
他们经常会这样。例如,想象一些调试器试图以有用的方式打印回溯。它将想要在回溯中的过程对象和它们的名称之间进行映射。该映射要么指向 'wrong' 名称,要么指向所有名称,然后您必须知道您实际使用了哪个名称。
这是 Racket 中的示例:
> (object-name cons)
'cons
> (define make-thingy cons)
> (object-name make-thingy)
'cons
在 Common Lisp 中也可以做到这一点。我们可以设置符号的符号功能。
(setf (symbol-function 'numer)
(function car))
另一种方法是定义这些函数:
(defun numer (rat)
(car rat))
现在会有调用这些额外函数的开销。这可以在开发和调试期间提供帮助。
在 Common Lisp 中,可以给编译器一个提示,它可以内联函数:
(declaim (inline numer))
然后在用于生产或交付的优化编译代码中,可以内联函数:函数调用开销不会存在,但调用将不再可见。
Does this advice still apply? For example, are modern debugging aids still defeated in this way?
普通 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
会在 [= 周围执行一些跟踪代码20=],但对 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.
当使用 encapsulate
和 NIL 时,跟踪 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
未追踪:
USER> (trace)
(BAR)
USER> (untrace foo)
T
USER> (bar)
1
小鸡计划
例如,在 Chicken Scheme 中,跟踪是 an extension that relies on advice,它又依赖于一个内部机制来改变过程(在过程中提到了一个 forward-table),这意味着程序本身(不是它的名称)正在执行跟踪。
这看起来很像上面的 :encapsulate nil
案例。
结论
我不会依赖这个别名来工作,因为它看起来有点脆弱,而不是 table。 Common Lisp 允许您以类似于函数的方式定义访问器,但在编译期间会扩展(使用 inline
或 define-compiler-macro
),以防您担心性能。在 Scheme 中,您可以做同样的事情,或者加载一个不同的文件,在您交付程序时创建别名而不是包装器。
我还认为最好避免优化,除非您可以在测试时确定实际瓶颈。
计算机程序的结构和解释有以下footnote:
Another way to define the selectors and constructor is
(define make-rat cons)
(define numer car)
(define denom cdr)
The first definition associates the name
make-rat
with the value of the expressioncons
, which is the primitive procedure that constructs pairs. Thusmake-rat
andcons
are names for the same primitive constructor.Defining selectors and constructors in this way is efficient: Instead of
make-rat
callingcons
,make-rat
iscons
, so there is only one procedure called, not two, whenmake-rat
is called. On the other hand, doing this defeats debugging aids that trace procedure calls or put breakpoints on procedure calls: You may want to watchmake-rat
being called, but you certainly don't want to watch every call tocons
.
这个建议还适用吗?比如,现代的调试辅助工具还这样被打败吗?
他们经常会这样。例如,想象一些调试器试图以有用的方式打印回溯。它将想要在回溯中的过程对象和它们的名称之间进行映射。该映射要么指向 'wrong' 名称,要么指向所有名称,然后您必须知道您实际使用了哪个名称。
这是 Racket 中的示例:
> (object-name cons)
'cons
> (define make-thingy cons)
> (object-name make-thingy)
'cons
在 Common Lisp 中也可以做到这一点。我们可以设置符号的符号功能。
(setf (symbol-function 'numer)
(function car))
另一种方法是定义这些函数:
(defun numer (rat)
(car rat))
现在会有调用这些额外函数的开销。这可以在开发和调试期间提供帮助。
在 Common Lisp 中,可以给编译器一个提示,它可以内联函数:
(declaim (inline numer))
然后在用于生产或交付的优化编译代码中,可以内联函数:函数调用开销不会存在,但调用将不再可见。
Does this advice still apply? For example, are modern debugging aids still defeated in this way?
普通 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
会在 [= 周围执行一些跟踪代码20=],但对 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.
当使用 encapsulate
和 NIL 时,跟踪 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
未追踪:
USER> (trace)
(BAR)
USER> (untrace foo)
T
USER> (bar)
1
小鸡计划
例如,在 Chicken Scheme 中,跟踪是 an extension that relies on advice,它又依赖于一个内部机制来改变过程(在过程中提到了一个 forward-table),这意味着程序本身(不是它的名称)正在执行跟踪。
这看起来很像上面的 :encapsulate nil
案例。
结论
我不会依赖这个别名来工作,因为它看起来有点脆弱,而不是 table。 Common Lisp 允许您以类似于函数的方式定义访问器,但在编译期间会扩展(使用 inline
或 define-compiler-macro
),以防您担心性能。在 Scheme 中,您可以做同样的事情,或者加载一个不同的文件,在您交付程序时创建别名而不是包装器。
我还认为最好避免优化,除非您可以在测试时确定实际瓶颈。