3
votes

In Scheme R7RS there is both a load and include form.

Include is described as:

Semantics: Both include and include-ci take one or more filenames expressed as string literals, apply an implementation-specific algorithm to find corresponding files, read the contents of the files in the specified order as if by repeated applications of read, and effectively re- place the include or include-ci expression with a begin expression containing what was read from the files. The difference between the two is that include-ci reads each file as if it began with the #!fold-case directive, while include does not. Note: Implementations are encouraged to search for files in the directory which contains the including file, and to provide a way for users to specify other directories to search.

Load is described as:

An implementation-dependent operation is used to trans- form filename into the name of an existing file con- taining Scheme source code. The load procedure reads expressions and definitions from the file and evalu- ates them sequentially in the environment specified by environment-specifier. If environment-specifier is omitted, (interaction-environment) is assumed. It is unspecified whether the results of the expres- sions are printed. The load procedure does not af- fect the values returned by current-input-port and current-output-port. It returns an unspecified value. Rationale: For portability, load must operate on source files. Its operation on other kinds of files necessarily varies among implementations.

What is the rationale for the two forms? I assume it is historic. Is there any import semantic difference between the two forms? I see that load can optionally include an environment specifier and include doesn't have that. And include-ci has no direct equivalent using load. But comparing load and include alone, what is the difference and is it important?

2

2 Answers

2
votes

Historically, Lisp implementations did not offer module systems.

Large programs used load in order to run a set of instructions, the load function runs a REPL script by reading S-expressions from a file, one by one, and passing them to eval.

Include, on the other hand, is used to inline the code read from a file into the your code. It does not evaluate the code.

...replace the include or include-ci expression with a begin expression containing what was read from the files

The added 'begin' prepares the code read from the file to be evaluated sequentially.

Sources: Question quotes ,Racket docs

2
votes

I think the critical difference is that include is syntax (or in traditional Lisp terms, it is a macro) while load is a function. In traditional Lisp terms (there will be a much more formal definition of this in Scheme terms which I am not competent to give) this means that include does its work at macro-expansion time, while load does its work at evaluation time. These times can be very different for an implementation which has a file compiler: macro-expansion time happens during compilation of files, while evaluation happens only much later, when the compiled files are loaded.

So, if we consider two files, f1.scm containing

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

and f2.scm containing

(define bar 2)

then if you load, or compile f1.scm it is exactly the same as if you had loaded or compiled a file fe.scm which contained:

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

which in turn is the same as if fe.scm contained:

(define foo 1)
(define bar 2)

In particular this inclusion of the files happens at macro-expansion time, which happens when the compiler runs: the object file (fasl file) produced by the compiler will include compiled definitions of foo and bar, and will not in any way depend on f2.scm or its compiled equivalent existing.

Now consider f3.scm containing:

(define foo 1)
(load "f2")

(note I have assumed that (load "f2") (as opposed to (load "f2.scm")) loads the compiled file if it can find it, and the source file if it can't: I think this is implementation-dependent).

Loading the source of this file will do the same thing as loading f1.scm: it will cause foo and bar to be defined. But compiling this file will not: it will produce a compiled file which, when it is later loaded will try to load either the source or compiled versions of f2.scm. If that file exists, at load time, then it will be loaded and the effect will be the same as the include case. If it does not exist at load time, bad things will happen. Compiling f1.scm will not cause the definitions in f2.scm to be compiled.


Depending on your background it might be worth comparing this to, say, C-family languages. What include does is what #include does: it splices in source files as they are read, and in C (as in many Scheme/Lisp systems) this happens as the files are compiled. What load does is to load code at runtime, which, in C, you would need to do by invoking the dynamic linker or something.