是否可以更改 DrRacket/Scheme search/reference 图书馆的顺序?

Is it possible to change the order DrRacket/Scheme search/reference the library?

我按照 SICP 3.3.3 的说明创建了一个 table。

这里是code_0.scm:

;code_0.scm
#lang scheme
(require rnrs/base-6)
(require rnrs/mutable-pairs-6)

(define nil '())

(define (make-table)
  (list '*table*))

(define (assoc key records)
  (cond ((null? records)
         false)
        ((equal? key (caar records))
         (car records))
        (else
         (assoc key (cdr records)))))

(define (insert! key value table)
  (let ((record (assoc key (cdr table))))
    (if record
        (set-cdr! record value)
        (set-cdr! table
                  (cons (cons key value)
                        (cdr table)))))
  'OK)

(define (lookup key table)
  (let ((record (assoc key (cdr table))))
    (if record
        (cdr record)
        false)))


(define table (make-table))

(insert! 0 0 table)
(insert! 1 1 table)
(insert! 2 2 table)

code_0.scm 运行良好,但在成为 code_1.scm:

的外部参考文件后就不行了

;我此时删除code_0.scm处的#lang scheme

;code_1.scm
#lang scheme/load
(load "code_0.scm")

(define table-0 (make-table))
(insert! 0 0 table-0)
(insert! 1 1 table-0)
(insert! 2 2 table-0)

DrRacket 中显示错误:

assoc: not a proper list: {{0 . 0}}

根据 我拉起,这是因为 "assoc" 函数已经在 Scheme 库(或 DrRacket 库?)中定义,编译器选择 link standard/system 比我早的

那么,是否可以更改 DrRacket/Scheme search/reference 图书馆的顺序?

如果是,怎么办?

如果不是,这是编译器或语言的缺陷吗?

如果我必须构建名称重复的函数,除了在 "main" 文件中实现它之外,还有其他方法可以避免这种情况吗?

我没有收到任何错误 运行 你在 DrRacket 中的文件。load 的文件可能是上一个问题的旧文件。这是唯一合乎逻辑的结论,因为您在此处列出的文件中 none 使用 assoc,但旧文件使用。

不建议将Scheme和Racket混用。 #lang scheme(或 #!scheme)是今天 #!racket 的旧名称,它导入当前在 #!racket 中的所有符号,并且您的 require 语句是除了那。

rnrs 下的库是供 R6RS 语言使用的,因此第一行应该是 #!r6rs 而不是要求你使用 (import (rnrs base) (rnrs mutable-pairs))。在 R6RS 中你可以导入除了一些符号。例如。 (import (except (rnrs base) cons)) 不导入 cons 但其他所有内容。我没有使用 assoc 因为 (rnrs base)(rnrs mutable-pairs) 中没有 assoc 所以它可能来自 #!scheme (#!racket)

如果您打算将您的词典用作一个库,您可以将它变成一个 R6RS 库并导入它,而不是使用 load

另请注意,Racket 中也有一个 SICP compatibility language。它基于 #!r5rs,书中的大多数示例也适用!

您看到的错误 assoc: not a proper list: {{0 . 0}} 需要解释。

在 Racket 语言中,您可以使用 cons 创建不可变对,使用 mcons 创建可变对。通常在 Scheme 语言中 cons 也会创建不可变对 - 但是因为你有行

(require rnrs/base-6)
(require rnrs/mutable-pairs-6)

所有标准列表函数都替换为创建可变函数的函数。

请注意,可变和不可变对是两种完全不同的数据类型 - 尽管它们都称为对。

系统中的某处定义了原语,例如 prim-mconsprim-icons,它们创建可变和不可变对。 #lang schemecons 绑定到 prim-mcons#lang racketcons 绑定到 prim-icons

这对处理列表的所有标准函数都有影响。 rnrs/mutable-pairs-6 中的 assoc 需要一个由可变对组成的列表,而 Racket assoc 需要一个由不可变对组成的列表。因此错误。

如何发现这一点?在可以同时使用 mconscons 的 Racket 语言中,标准列表操作创建具有不可变对的列表,打印机使用 (...) 打印它们。可变对用花括号打印 {...}.

错误

assoc: not a proper list: {{0 . 0}}

显示带大括号的关联列表。这意味着关联列表是在 Scheme 中使用可变对创建的。

要解决您的问题,您需要使用正确的 assoc。您可以通过添加行

来做到这一点
(require rnrs/base-6)
(require rnrs/mutable-pairs-6)

还有你的第二个文件。

注意:#lang scheme语言不是RnRS Schemes之一,而是项目更名前Racket使用的方言。

注意 2:为什么 Racket 首先从对中删除可变性?好吧,在写得很好的 Scheme 代码中很少看到 set-car!set-cdr! 被使用。保证一对永远不会改变允许各种优化,允许大多数程序 运行 更快(对于一个 length 可以实现使用恒定时间)。因此,我们选择将不可变对作为标准,并为那些绝对需要它们的程序保留可变对。

这不是您问题的直接答案,而是对问题的描述 你把自己弄得一团糟,最终遇到了那个问题, 并且还会导致其他问题。

我将首先尝试非常简要地解释 Racket 如何评估模块, 因为它与理解混乱有关。看起来你是 尝试使用 "just scheme" 所以你可能不太感兴趣 this,但您仍然应该阅读它以了解您遇到的问题。 (即使你解决了那个特定的问题。)

.rkt 个文件的通常情况下,每个文件都单独评估 命名空间,由来自 (1) #lang 的绑定填充 指定,以及 (2) 您 require 的各种库(= 其他模块)。 所以当你有

#lang foo
(require bar)

你从一个新的命名空间开始,从 foo 获取所有绑定 到此命名空间,然后添加来自 bar 的绑定。如果 在冲突中,require 绑定将从语言中隐藏一些, 所以如果他们都提供了一些功能f,你的代码的名称 将使用 bar 中的那个。如果您需要多个 图书馆:

#lang foo
(require bar baz)

并且barbaz都提供f,那么你会得到一个错误if 这些是不同的 f。例如,如果 bar 提供内置 consbaz 提供 cons 创建 可变对(即,提供内置 mcons 作为 cons)。你会 not 如果它们都提供相同的 f,则会出错。

再次注意,这与 "initial bindings" 不同 你从 #lang 中得到的 - 后者 require 只会影子 如果使用相同的名称。 #lang 绑定的原因 区别在于它们提供了一些基本的绑定 代码在基本层面上使用。例如,语言绑定 将为您提供稍后使用的 require。另一个例子是 #%module-begin 包裹整个模块主体的宏——一个工具 可用于将所有表达式转换为其他内容,例如 例如,racket 语言中的顶级表达式是这样得到的 他们的价值印刷。

粗略地说,球拍中的库(=模块)分为 语言模块和库模块。这不是什么 正式的,因为两者都以相同的方式实现:提供的模块 东西。区别在于他们提供的东西的种类,在哪里 语言模块通常会提供很多绑定,包括基本的 require#%module-begin 之类的,以及您期望的东西 来自 defineifcond+cons 等口齿不清的语言

因此,在通常情况下,您不会 运行 陷入名称问题 冲突太多,因为图书馆试图避免使用通用名称。但如果你 尝试 require 一个语言模块就好像它是一个库,你很快 运行 进入此类问题,因为语言模块往往会提供很多 名字,包括那些非常常见的名字,比如我上面列出的那些。

现在您可以在 code0.scm:

中看到这是一个问题
#lang scheme
(require rnrs/base-6)
(require rnrs/mutable-pairs-6)

这样做是首先使用 scheme 绑定。这个scheme 语言 不是 标准方案 -- 它是 racket 的前身 语言,可以追溯到更名之前,当时 Racket 被称为 PLT Scheme,因此 scheme 旨在成为“方案方言 PLT Scheme 默认使用”。除其他外,它具有不可变的 对,与您在 #lang racket 文件中得到的相同。

但后来你堆积了大部分 rnrs 绑定——那些使用 mutable 对,如您所见,它们是不同的类型。 您需要一个通常用作语言的模块,因此 most 您使用的绑定将正常工作,但迟早您会 运行 进入来自 scheme 的绑定,该绑定未被来自 rnrs 的绑定覆盖, 如果它与配对有关,你就会遇到问题。

所以这里的结论是避免像这样混淆两种语言: 坚持 #lang scheme#lang r6rs。在前一种情况下, 你也可以切换到 #lang racket,并使用通常的球拍 图书馆,在后一种情况下你应该小心并尽量避免 导入期望不可变对(以及更多)的球拍库。 或者,如果您的目标是进行一些 SICP,则使用 the SICP Neil Van Dyke 的语言 写了。

但是你还有更多的问题。在您的 code_1.scm 中,您正在使用 scheme/load 作为语言,这可能会成功 可能做你想做的事——但它带来了一个整体 一堆其他问题。我你查找文档 scheme/load(这会将您转到 racket/load 的文档, 更现代的名字),你会看到一些关于 evalload 的东西。这个 是因为在某些时候人们希望能够写一个文件 里面有几个模块,但那是不可能的。 (现在它是, 你得到了子模块——但 racket/load 仍然存在。) scheme/load 的实施是为了解决这个问题:使用它就像您 在单个 REPL 中输入表达式,因此您可以定义一堆 模块并使用它们。但正因为如此,这是一种奇怪的语言,如果 你不想要那个特定的功能,你应该避免它。

名字里的load其实是应该的 阻止人们使用它的东西......问题是 load 是一个古老而原始的代码结构工具,它是 只有 R5RS 之前的标准语言可用。事情 它确实是(同样,这是一个粗略的描述)read 表达式 从一个文件中 evaluate 它们。问题是你得到一个 一切的命名空间,更糟的是,每个定义实际上可以是一个 先前定义的突变(如果存在)。这意味着即使 像

这样的简单定义
(define (add1 x) (+ 1 x))

不安全,因为稍后的 load 文件可以以某种方式重新定义 + 这打破了这个定义。简而言之,这是一团糟 世界上许多图书馆为您提供相同的名字但 不同的语义。 IOW,Racket 通常是一团糟。球拍(和 后来,R6RS)以一种将整个事物分类为一个方式的方式使用模块 更好的方法——没有单一的命名空间和变异,只有名字 由封闭模块提供。实际上,突变的事情是 仍然存在,但它在你能受到的伤害方面受到更多限制(它是 仅在 REPL 中使用);还有 loadeval, 通常避免将它们作为组织源文件的工具。

使用 load 还解释了为什么您必须删除 #lang 行 你已经用过了——当你 load 一个带有模块定义的文件时,你 得到 that 定义 -- 模块的。实际使用这些东西 该模块提供的,您还需要添加一个 (require 'code_0)。 还有一件事是删除 #lang 行通常是 灾难,因为你没有任何必要的绑定 一段合理的代码——但在你的情况下,你 require 做了一个整体 后来的语言,这就是事情继续运作的方式,只有 细微差别。

所以第二个高层次的结论是避免load——这是一个不好的 工具。另外,避免使用 scheme/loadracket/load 语言,除非 您真的知道他们在做什么并且需要该功能。