我按照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”文件中实现它吗?
答案 0 :(得分:10)
这不是您问题的直接答案,而是对此问题的描述 搞砸了你自己最终解决了这个问题, 并且还会导致其他问题。
我将首先解释一下Racket如何非常简短地评估模块, 因为它与理解混乱有关。它看起来像你 试图使用"只是计划"所以你可能不太感兴趣 这个,但你仍然应该阅读它,以了解你所处的问题。 (即使你解决了那个问题。)
在.rkt
文件的通常情况下,每个文件都是自己评估的
命名空间,由(1)您#lang
的绑定填充
指定,以及(2)您require
的各种库(=其他模块)。
所以,当你有
#lang foo
(require bar)
从一个新的命名空间开始,从foo
获取所有绑定
进入此命名空间,然后添加bar
的绑定。如果
冲突,require
绑定会影响语言,
所以,如果它们都提供了一些函数f
,那么代码的名称
将使用来自bar
的那个。如果您需要多个
库:
#lang foo
(require bar baz)
bar
和baz
都提供了f
,如果,您将收到错误
这些是不同的f
。例如,bar
可能会发生这种情况
提供内置cons
和baz
提供创建的cons
可变对(即,将内置mcons
提供为cons
)。你会
不如果两者都提供相同的f
,则会收到错误。
再次注意,这与"初始绑定"不同。是的
你从#lang
得到的东西 - 后者require
只会影子
如果使用相同的名称,他们。 #lang
绑定的原因
区别对待的是它们提供了一些基本的绑定
代码在基础层面使用。例如,语言绑定
将为您提供稍后使用的require
。另一个例子是
包裹整个模块体的#%module-begin
宏 - 一个工具
可用于将所有表达式转换为其他表达式
例如,这是如何用racket
语言获取表达式
他们的价值印刷。
粗略地说,球拍中的库(=模块)被分成了
语言模块和库模块。这不是那样的
正式,因为两者都以相同的方式实现:提供的模块
东西。不同之处在于它们提供的东西,在哪里
语言模块通常会提供很多绑定,包括基本绑定
像require
和#%module-begin
之类的东西,以及您期望的东西
来自lpish语言,如define
,if
,cond
,+
,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
的文档
更现代的名称),您会看到有关eval
和load
的一些内容。这个
是因为在某些时候人们希望能够写一个文件
其中有几个模块,但这是不可能的。 (现在它是,
你得到了子模块 - 但racket/load
仍然存在。)
实施scheme/load
是为了解决这个问题:使用它就好像你一样
在单个REPL中输入表达式,因此您可以定义一堆
模块并使用它们。但是因为这个,它是一种奇怪的语言,如果
你不想要那个特殊的功能,你应该避免它。
名称中的load
应该是实际存在的
阻止人们使用它的东西......事情就是这样
load
是用于构造代码的古老而原始的工具
只有标准语言中可用的东西,最高可达R5RS。事情
它确实是(再次,这是一个粗略的描述)read
表达式
从文件和eval
uate他们。问题是你得到一个
一切都是命名空间,更糟糕的是,每个定义实际上都是一个
如果存在,则先前定义的变异。这意味着即使
简单的定义,如
(define (add1 x) (+ 1 x))
不安全,因为文件的后期load
可以某种方式重新定义+
这打破了这个定义。简而言之,这非常混乱
许多图书馆为您提供相同名称的世界
不同的语义。现在,它通常在Racket中一团糟。球拍(和
之后,R6RS)以一种方式对模块进行排序
更好的方法 - 没有单一的命名空间和变异,只是名称
由封闭模块提供。 Actualy,突变的事情是
仍然在那里,但它受到的伤害更受限制(它可以获得)
仅用于REPL); load
和eval
一起也是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-mcons
和prim-icons
,它们创建了可变和不可变的对。 #lang scheme
将cons
绑定到。{1}}
prim-mcons
和#lang racket
将cons
绑定到prim-icons
。
这会对列表上的所有标准函数产生影响。来自assoc
的{{1}}期望由可变对组成的列表,并且Racket rnrs/mutable-pairs-6
期望由不可变对组成的列表。因此错误。
怎么能发现这个?在Racket语言中,您可以同时使用assoc
和mcons
标准列表操作创建具有不可变对的列表,打印机使用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 scheme
和set-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
所以它可能来自#!racket
(load
)
如果您计划将字典用作库,则可以将其设为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" />
,本书中的大多数示例也适用!