是否可以更改DrRacket / Scheme搜索/引用库的顺序?

时间:2015-07-26 15:49:52

标签: scheme racket sicp

我按照SICP 3.3.3的说明创建一个表格。

以下是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的外部参考文件后却没有:

;此时我在#lang scheme删除了code_0.scm

;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:不是正确的列表:{{0。 0}}

根据a previous question我提起,这是因为“assoc”函数已经在Scheme库(或DrRacket库?)中定义,编译器选择先将标准/系统链接到我的

那么,是否可以更改DrRacket / Scheme搜索/引用库的顺序?

若是,是吗?

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

如果我必须构建一个名称复制函数,还有另一种方法可以避免这种情况,除了在“main”文件中实现它吗?

3 个答案:

答案 0 :(得分:10)

这不是您问题的直接答案,而是对此问题的描述 搞砸了你自己最终解决了这个问题, 并且还会导致其他问题。

我将首先解释一下Racket如何非常简短地评估模块, 因为它与理解混乱有关。它看起来像你 试图使用"只是计划"所以你可能不太感兴趣 这个,但你仍然应该阅读它,以了解你所处的问题。 (即使你解决了那个问题。)

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

#lang foo
(require bar)

从一个新的命名空间开始,从foo获取所有绑定 进入此命名空间,然后添加bar的绑定。如果 冲突,require绑定会影响语言, 所以,如果它们都提供了一些函数f,那么代码的名称 将使用来自bar的那个。如果您需要多个 库:

#lang foo
(require bar baz)

barbaz都提供了f,如果,您将收到错误 这些是不同的f。例如,bar可能会发生这种情况 提供内置consbaz提供创建的cons 可变对(即,将内置mcons提供为cons)。你会 如果两者都提供相同的f,则会收到错误。

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

粗略地说,球拍中的库(=模块)被分成了 语言模块和库模块。这不是那样的 正式,因为两者都以相同的方式实现:提供的模块 东西。不同之处在于它们提供的东西,在哪里 语言模块通常会提供很多绑定,包括基本绑定 像require#%module-begin之类的东西,以及您期望的东西 来自lpish语言,如defineifcond+cons等。

因此,在通常情况下,您不会遇到名称问题 由于图书馆试图避免使用常见名称,因此冲突太多。但如果你 尝试require一个语言模块就好像它是一个库,你很快 因为语言模块倾向于提供很多问题而遇到这样的问题 名称,包括我上面列出的那些非常常见的名称。

现在,您可以在code0.scm

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

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

但是后来你堆积了大部分rnrs绑定 - 而且那些使用了 mutable 对,正如您所发现的那样,是一种不同的类型。 您需要一个通常用作语言的模块,因此大多数 你使用的绑定工作正常,但迟早你会运行 来自scheme的{​​{1}}未绑定rnrs的绑定 如果它与配对有关,那么你就会遇到问题。

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

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

名称中的load应该是实际存在的 阻止人们使用它的东西......事情就是这样 load是用于构造代码的古老而原始的工具 只有标准语言中可用的东西,最高可达R5RS。事情 它确实是(再次,这是一个粗略的描述)read表达式 从文件和eval uate他们。问题是你得到一个 一切都是命名空间,更糟糕的是,每个定义实际上都是一个 如果存在,则先前定义的变异。这意味着即使 简单的定义,如

(define (add1 x) (+ 1 x))

不安全,因为文件的后期load可以某种方式重新定义+ 这打破了这个定义。简而言之,这非常混乱 许多图书馆为您提供相同名称的世界 不同的语义。现在,它通常在Racket中一团糟。球拍(和 之后,R6RS)以一种方式对模块进行排序 更好的方法 - 没有单一的命名空间和变异,只是名称 由封闭模块提供。 Actualy,突变的事情是 仍然在那里,但它受到的伤害更受限制(它可以获得) 仅用于REPL); loadeval一起也是load, 并且通常将它们作为组织源文件的工具来避免。

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

因此,第二个高级结论是避免scheme/load - 这是一个坏事 工具。另外,请避免使用racket/load或{{1}}种语言 你真的知道他们正在做什么并且需要这些功能。

答案 1 :(得分:2)

您看到的错误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绑定到。{1}} prim-mcons#lang racketcons绑定到prim-icons

这会对列表上的所有标准函数产生影响。来自assoc的{​​{1}}期望由可变对组成的列表,并且Racket rnrs/mutable-pairs-6期望由不可变对组成的列表。因此错误。

怎么能发现这个?在Racket语言中,您可以同时使用assocmcons标准列表操作创建具有不可变对的列表,打印机使用cons打印它们。可变对用大括号(...)打印。

错误

{...}

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

要解决您的问题,您需要使用正确的assoc: not a proper list: {{0 . 0}} 。您可以通过添加行

来实现
assoc

也是你的第二个文件。

注意:(require rnrs/base-6) (require rnrs/mutable-pairs-6) 语言不是RnRS方案之一,而是Racket在项目更改名称之前使用的方言。

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

答案 2 :(得分:1)

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

将Scheme和Racket混合起来是不可取的。 #!scheme(或#!racket)是今天#!racket的旧名称,它导入当前位于require的所有符号,并且您的rnrs语句是那。

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

如果您计划将字典用作库,则可以将其设为R6RS库并导入而不是使用#!r5rs

另请注意,Racket中也有SICP compatibility language。它基于 <img style="vertical-align:top" src="https://www.getdesignschool.com/wp-content/uploads/2015/07/bottom.jpg" alt="" width="56" height="44" /> ,本书中的大多数示例也适用!