在Scheme R7RS中,有load
和include
形式。
包含被描述为:
语义:include和include-ci取一个或 更多文件名表示为字符串文字,请应用 特定于实现的算法,用于查找相应的文件,读取 指定顺序的文件内容,就像重复一样 阅读的应用,并有效地替换包含或 include-ci表达式,其中包含一个包含所读内容的begin表达式 从文件。两者之间的区别在于include-ci 读取每个文件,就好像它以#!fold-case指令开头一样 包括没有。注意:鼓励实现搜索 目录中包含包含文件的文件,以及 为用户提供了一种指定其他目录进行搜索的方法。
负载描述为:
依赖于实现的操作用于转换文件名 到包含Scheme源代码的现有文件的名称。该 加载过程从文件中读取表达式和定义 在指定的环境中按顺序评估它们 环境说明符。如果省略环境说明符, 假设(交互环境)。没有说明是否 表达的结果被打印出来。加载程序没有 影响current-input-port和。返回的值 电流 - 输出端口。它返回一个未指定的值。理由:为 可移植性,加载必须对源文件进行操作。它在其他的操作 各种文件必然因实现而异。
这两种形式的基本原理是什么?我认为这是历史性的。这两种形式之间是否有任何导入语义差异?我看到load
可以选择包含环境说明符,include
没有。 include-ci
使用load
没有直接对等关系。但仅比较load
和include
,有什么区别并且重要吗?
答案 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.scm
和foo
的定义。但是编译这个文件不会:它会产生一个编译文件,以后加载会尝试加载bar
的源版本或编译版本。如果该文件存在,则在加载时,它将被加载,效果将与f2.scm
情况相同。如果在加载时不存在,则会发生不好的事情。编译include
不会导致f1.scm
中的定义被编译。
根据您的背景,可能值得将其与C系列语言进行比较。 f2.scm
做的是include
做什么:它在读取时在源文件中拼接,在C中(如在许多Scheme / Lisp系统中),这会在编译文件时发生。 #include
做的是在运行时加载代码,在C中,您需要通过调用动态链接器或其他东西来完成。