Scheme R7RS中load与include的差异及存在合理性问询
load vs include in Scheme R7RS Let's break down your questions about why both forms exist in R7RS, their historical context, and their critical semantic differences.
Why Both Exist: Rationale & Historical Roots
Yes, part of their coexistence stems from Scheme's evolutionary history, but they also serve distinct, complementary use cases:
loadis a long-standing feature of Scheme, dating back to early implementations like MIT's original Scheme. It was designed for dynamic runtime code loading—perfect for extending a running session, executing scripts, or testing code without restarting the interpreter.include(andinclude-ci) were added later during standardization to fill a gap: static, compile-time (or macro-expansion-time) code inclusion. Beforeinclude, developers relied on implementation-specific hacks to split code across files while treating them as a single logical unit during compilation.
The R7RS spec explicitly frames include as a way to integrate code into the current program as if it were written inline, while load is for evaluating code in a targeted environment at runtime. This split aligns with Scheme's core philosophy of supporting both static and dynamic programming styles.
Key Semantic Differences Between load and include
Beyond the environment parameter you noted, there are critical distinctions that shape their use cases:
1. Execution/Expansion Timing
include: Runs during macro expansion. It reads the target file(s), parses their content, and replaces theincludeexpression with abeginblock containing those parsed forms. The included code is treated exactly like code written directly in the parent file—syntax checks, macro expansions, and static optimizations apply to it as part of the parent compilation unit.load: Runs at runtime. It reads the file's expressions, then evaluates each one sequentially in the specified environment (or the default interaction environment). The code isn't integrated into the parent program's compilation; it's executed dynamically.
2. Environment Control
include: Has no environment parameter. The included code is expanded and evaluated in the same lexical and dynamic environment where theincludeexpression appears.load: Lets you specify anenvironment-specifierto isolate loaded code. For example, you can load a script into a sandboxed environment without polluting the main interaction space.
3. Text Processing Behavior
include: Offersinclude-ci, which treats the target file as if it starts with#!fold-case(making identifiers case-insensitive). There's no equivalent forload—it always reads files according to the current reader settings.load: Guarantees not to modify the current input/output ports, per the R7RS spec.includedoesn't interact with ports at all, since it's a static text replacement.
4. Return Values & Side Effects
include: Resolves to the value of the last form in the generatedbeginblock, just like a regularbeginexpression.load: Its return value is unspecified, and whether expression results are printed depends on the implementation. It also preserves the state of input/output ports, which is useful for scripts that need to maintain port consistency.
Is the Difference Important?
Absolutely—these differences make each form irreplaceable for specific scenarios:
- Use
includewhen you want to split code across files but treat them as a single logical program during compilation. This is common for libraries, where splitting modules into separate files while compiling them together ensures performance and consistency. - Use
loadwhen you need dynamic code loading: for example, loading plugins at runtime, executing user-provided scripts in a sandbox, or quickly testing snippets in an interactive REPL.
The environment control alone is a major differentiator—if you need to isolate loaded code, load is your only option. Conversely, if you want static code integration with compile-time checks, include is the right choice.
内容的提问来源于stack exchange,提问作者eatonphil

