方案R7RS中加载和包含之间的差异

时间:2018-01-23 14:45:20

标签: scheme lisp r7rs

在Scheme R7RS中,有loadinclude形式。

包含被描述为:

  

语义:include和include-ci取一个或   更多文件名表示为字符串文字,请应用   特定于实现的算法,用于查找相应的文件,读取   指定顺序的文件内容,就像重复一样   阅读的应用,并有效地替换包含或   include-ci表达式,其中包含一个包含所读内容的begin表达式   从文件。两者之间的区别在于include-ci   读取每个文件,就好像它以#!fold-case指令开头一样   包括没有。注意:鼓励实现搜索   目录中包含包含文件的文件,以及   为用户提供了一种指定其他目录进行搜索的方法。

负载描述为:

  

依赖于实现的操作用于转换文件名   到包含Scheme源代码的现有文件的名称。该   加载过程从文件中读取表达式和定义   在指定的环境中按顺序评估它们   环境说明符。如果省略环境说明符,   假设(交互环境)。没有说明是否   表达的结果被打印出来。加载程序没有   影响current-input-port和。返回的值   电流 - 输出端口。它返回一个未指定的值。理由:为   可移植性,加载必须对源文件进行操作。它在其他的操作   各种文件必然因实现而异。

这两种形式的基本原理是什么?我认为这是历史性的。这两种形式之间是否有任何导入语义差异?我看到load可以选择包含环境说明符,include没有。 include-ci使用load没有直接对等关系。但仅比较loadinclude,有什么区别并且重要吗?

2 个答案:

答案 0 :(得分:1)

历史上,Lisp实现不提供模块系统。

大型程序使用加载来运行一组指令,加载函数通过逐个读取文件中的S表达式来运行 REPL脚本,并将它们传递给eval。

另一方面,

包含用于将从文件中读取的代码内联到您的代码中。它不会评估代码。

  

...将include或include-ci表达式替换为包含从文件中读取内容的begin表达式

添加的'开始'准备从文件中读取的代码,以便顺序评估。

来源:问题引用,Racket docs

答案 1 :(得分:1)

我认为关键的区别在于include语法(或者在传统的Lisp术语中,它是一个宏),而load是一个函数。在传统的Lisp术语中(在Scheme方面,我将无法给出更正式的定义)这意味着include在宏扩展时完成它的工作,而load确实它在评估时的工作。对于具有文件编译器的实现,这些时间可能非常不同:在编译文件期间发生宏扩展时间,而评估在加载编译文件后很晚才发生。

因此,如果我们考虑两个文件,f1.scm包含

(define foo 1)
(include "f2.scm")

f2.scm包含

(define bar 2)

然后,如果你加载,或编译 f1.scm,它就像加载或编译包含以下内容的文件fe.scm完全相同:

(define foo 1)
(begin
  (define bar 2))

反过来与fe.scm包含的内容相同:

(define foo 1)
(define bar 2)

特别是这些文件包含在宏扩展时发生,这在编译器运行时发生:编译器生成的目标文件(fasl文件)将包含foo和{{1}的编译定义},并且不会以任何方式依赖于bar或其编译的等效存在。

现在考虑f2.scm包含:

f3.scm

(注意我假设(define foo 1) (load "f2") (而不是(load "f2"))加载编译后的文件(如果可以找到它),如果不能加载源文件:我认为这是实现 - 依赖性)。

加载此文件的源代码与加载(load "f2.scm")的操作相同:它将导致f1.scmfoo的定义。但是编译这个文件不会:它会产生一个编译文件,以后加载会尝试加载bar的源版本或编译版本。如果该文件存在,则在加载时,它将被加载,效果将与f2.scm情况相同。如果在加载时不存在,则会发生不好的事情。编译include不会导致f1.scm中的定义被编译。

根据您的背景,可能值得将其与C系列语言进行比较。 f2.scm做的是include做什么:它在读取时在源文件中拼接,在C中(如在许多Scheme / Lisp系统中),这会在编译文件时发生。 #include做的是在运行时加载代码,在C中,您需要通过调用动态链接器或其他东西来完成。