对标准程序的重新定义是否会改变其他标准程序的行为



假设我们使用以下内容之一重新定义标准过程car

(define (car)
(display "vroom vroom!"))

(set! car
(lambda ()
(display "vroom vroom!")))

在符合标准的Scheme实现中,以这种方式重新定义car是否会影响碰巧使用car定义的其他标准过程的行为?

例如,是否允许Scheme实现在内部将cadr定义为(define (cadr x) (car (cdr x))),以便用户在REPL对car的重新定义将改变cadr在REPL的行为?

不同的修订版有不同的答案

R5RS及更早版本:

不允许define现有绑定。因此,根据报告,您的第一次尝试是不正确的Scheme,运行它的结果可能是任何结果。一个segfault是可以的,用信号通知一个错误是可以的;忽略它是可以的

您只允许set作为顶级绑定,但它需要与原始绑定向后兼容。不允许用你的方式定义car,但允许这样做:

(let ((orig-car car))
(set! car 
(lambda (o)
(if (vector? o)
(vector-ref o 0)
(orig-car o)))))
(car '(1 2 3))       ; ==> 1
(car (vector 1 2 3)) ; ==> 1

实现可以自由地不断折叠和编译。因此,在现实中,它可能永远不会使用您的新版本,但该报告并没有阻止实现以您编写它的方式实现cadr,然后将使用car的新实现。只要你坚持标准并使其兼容,结果是可以预测的。

R6RS及更高版本:

从R6RS我们有库,我们可以重命名in和out,重写它不会在其他库中泄漏。因此,您可以使用set!car,但它不会泄漏,因此cadr将开始使用它,即使实现是这样做的cadr。这样就不需要向后兼容了。

通常你可以制作自己的列表库,在你的代码中不要使用标准,而是你的新版本,它会使用它。链接是相当静态的(显式导入),因此绑定永远不会与标准库绑定混淆。

否。第一个实例((define (car) ...)结合";汽车;到一个新的位置,遮蔽标准绑定。在实现中使用";汽车;((define (cadr x) ...);汽车;具有原始绑定。

CCD_ 19;汽车;具有标准绑定,必须发出错误信号:

% scheme
Chez Scheme Version 9.5.7.6
Copyright 1984-2021 Cisco Systems, Inc.
> (display car)
#<procedure car>
> (set! car cdr)
Exception: attempt to assign immutable variable car

搜索";不可变的";在标准报告中,例如r6rs:";所有显式导出的变量在导出库和导入库中都是不可变的"(因此基本库无法导出可变的car)

是。整个系统的行为可以更改。但不是你怎么做的。

% mit-scheme
MIT/GNU Scheme running under GNU/Linux
....
1 ]=> (pe)
;Value: (user)
1 ]=> (ge system-global-environment)
;Package: ()
;Value: #f
1 ]=> (pe)
;Value: ()
1 ]=> (define + 1)
;Value: +

移回用户环境(与初始REPL环境所指向的环境相同)。

1 ]=> (ge user-initial-environment)
;Package: (user)
;Value: #[environment 12]
1 ]=> (pe)
;Value: (user)
1 ]=> (+ 1 2) ; no more
;The object 1 is not applicable.
;To continue, call RESTART with an option number:
; (RESTART 2) => Specify a procedure to use in its place.
; (RESTART 1) => Return to read-eval-print level 1.
2 error> (assoc 'car 
(environment-bindings
system-global-environment))
;Value: (car #[compiled-procedure 1351
("list" #x1) #x1c #xe0f67c])

因此,您可以像更改+一样更改CAR(在system-global-environment中定义)。

但是,当然,这是一个坏主意,你的系统将不再工作。

有些内部过程使用的是经过编译的car+。那些被编译的将不会参考system-global-environment并继续工作。那些未编译的将崩溃。现在,根据您的系统是如何启动的,看看哪些引用了系统环境。如果某些库过程在没有编译的情况下加载到系统中,它肯定会崩溃。

注意,当我说";汇编";,这意味着除了调用explore/eval函数之外,通常还有其他解释方式,意味着绕过标准解释器。如果您使用的字节编译器的虚拟机引用了系统环境,则编译为字节码的过程将崩溃。如果代码完全编译到x86或mips,那么它有自己的引用,可以引用汇编程序中编写的标准库过程,并且不会引用全局环境。在计划体系中一切皆有可能。

如果在用户环境中重新定义过程(如您所做的那样),系统将继续工作,因为标准库过程指向系统环境,并且任何系统过程都不会看到用户环境。但是,您在用户环境中加载的所有过程都将使用您自己对car的定义。在这种情况下,可以重新定义事物。

请注意,命令(load "xxx")有一个名为environment的可选参数,您可以使用此选项告诉load要通过加载更改到什么环境。默认情况下,在批处理模式和REPL模式下,加载将写入其父环境为系统环境的用户环境。但是,您可以像load-option库那样使用技巧,并以非常复杂的方式(创建模块的库)操作环境。

最新更新