Scheme

Scheme 是一種函數語言程式設計語言，是 Lisp 的兩種主要方言之一，不同於與之並列的 Common Lisp，Scheme 遵循哲學，以一個小型語言核心作為標準，加上各種強力語言工具（語法糖）來擴充套件語言本身。Scheme 是第一個使用靜態作用域的 Lisp 方言，也是第一個引入頭等續體和 “乾淨宏” 的程式語言。

簡介

在 1975 年，麻省理工學院的傑拉德・傑伊・薩斯曼與蓋伊・史提爾二世，開發出了 Scheme 語言最初版本，隨後兩人透過發表「λ 論文集」而不斷對它進行完善和推廣。Scheme 與 λ 演算關係十分密切，故將小寫字母「λ」用作標誌。

麻省理工學院與其他一些院校，曾採用 Scheme 教授計算機科學入門課程。著名的入門教材《計算機程式的構造和解釋》（SICP），利用 Scheme 來詮釋程式設計。Scheme 有眾多實現可視為一個主要優勢，然而不同實現之間的差異成為了它的一個劣勢，Scheme 掌控委員會聲稱，它是 “世上最不可移植的程式語言”，並且是一個 “程式語言家族” 而非一個單一的語言。

歷史

}}

2.1 起源

Scheme 起源於 1958 年由約翰・麥卡錫提出的 Lisp 語言。麥卡錫透過 Lisp 證明瞭，經由幾個簡單的運算元，與用作匿名函式的借鑒自阿隆佐・邱奇的 λ 表示法 | title=Some History of Functional Programming Languages|url=https://www.cs.kent.ac.uk/people/staff/dat/tfp12/tfp12.pdf|quote=LISP was not based on the lambda calculus, despite using the word “LAMBDA” to denote functions. At the time he invented LISP, McCarthy was aware of (Church 1941) but had not studied it. The theoretical model behind LISP was Kleene’s theory of first order recursive functions. (McCarthy made these statements, or very similar ones, in a contribution from the floor at the 1982 ACM symposium on LISP and functional programming in Pittsburgh. No written version of this exists, as far as know.)|access-date=2021-11-10|archive-date=2020-04-15|archive-url=https://web.archive.org/web/20200415053847/https://www.cs.kent.ac.uk/people/staff/dat/tfp12/tfp12.pdf}}，就可以構建出圖靈完備的系統。麥卡錫提出的 S - 表示式，可以將程式與資料用相同的結構儲存，這被稱為同像性。Scheme 的語法即來自 S - 表示式，這一特性使得在 Scheme 中實現自迴圈直譯器變得非常簡單。

在 1973 年，麻省理工學院的提出的一種叫做演員模型的計算模型，並用 Lisp 開發當時叫做 - 73 的新語言來實現它

We shall assume that the reader is familiar with advanced pattern matching languages such as SNOBOL4, CONVERT, PLANNER-71, QA4, and POPLER.

We shall use (% A M%) to indicate sending the message M to the actor A. We shall use s1 s2 … sn to denote the finite squence s1, s2, … sn. ……

Identifiers can be created by the prefix operator =. ……

An expression (=> pattern body) is an abbreviation for (receive (message pattern) body) where receive is a more general actor that is capable of bindinq elements of the action in addition to the message.

Evaluating

(%(=> pattern body) the-message%), i.e. sending

(=> pattern body) the-message,

will attempt to match the-message against pattern, If the-message is not of the form specified by pattern, then the actor is NOT-APPLICABLE to the-message. If the-message matches pattern, the body is evaluated.……

Conceptually at least a new actor is created every time a message is sent. Consider sending to a target T a message M and a continuation C.

(send T

(message M

continuation C))

The transmission (% T M%) is an abbreviation for the above where C is defaulted to be the caller. If target T is the following:

(receive

(message the-body

continuation =the-continuation)

the-body)

then the the-body is evaluated in an environment where the-message is bound to M and the-continuation is bound to C.……

Many actors who are executing in parallel can share the same continuation. They can all send a message “return” to the same continuation. ”。

傑拉德・薩斯曼與蓋伊・史提爾為了理解演員模型，決定在 Maclisp 工作環境中實現一個微型 Lisp 直譯器，並接著增加建立演員和傳送訊息的機制。兩人很快發現了演員模型與 λ 演算之間的相似性，所謂「演員」就是彼得・蘭丁提出的閉包，而在 1970 年已將它介入 Lisp 用來解決 When a function uses free variables or when it calls functions which use them, then the value of this function is not completely determined by its arguments, since the value might depend on the values of the free variables. Thus in order to completely specify a computation, one has to specify a function and its arguments in addition to an environment in which the values of the free variables can be determined. ……An important point which must be realized about functional arguments (abbreviated FUNARGs) is that two different environments are involved in such cases. The first environment is the one which is in effect when then FUNARG is bound as an argument. We shall call this the binding environment. The second environment is the one that is in effect when the FUNARG is activated as a function. We shall call this the activation environment. ……Consider now what it would require for the system to restore the binding environment for functional arguments. It would require knowing where in the stack or "alist" the binding environment exists through some pointer to it. Supplying such pointer is a function FUNCTION in LISP. That is when one transmits a functional argument f which is to be evaluated in its binding environment, then one uses FUNCTION (f). FUNCTION will prevent its argument f from being evaluated, just as QUOTE would. The result of FUNCTION will be a structure which not only contains a reference to the function f, but also contains a pointer to the binding environment. Thus at the FUNARG's activation time we will be able to use the current place in the stack up to the point where the binding environment exists, thus skipping over any values in the activation environment which differ from those in the binding environment. ……The points we have made so far are:1) Free variables in function definitions require that one must have an environment in order to be able to evaluate a function.2) When one uses functional arguments, the the question arises as to which environment one chooses in order to evaluate the function, the binding environment or the activation environment.3) When one returns functions (which require the values of free variables for their evaluation) then one must, in general, save the binding environment and thus create a stack which is, in fact, a tree. ……LISP allows the user to choose between the binding environment and the activation environment by allowing a function to be bound with FUNCTION or with QUOTE. QUOTE will force the activation environment to be used in evaluating the function. A useful metaphor for the difference between FUNCTION and QUOTE in LISP is to think of QUOTE as a porous or an open covering of the function since free variables escape to the current environment. FUNCTION acts as a closed or nonporous covering (hence the term "closure" used by Landin). Thus we talk of "open" Lambda expressions (functions in LISP are usually Lambda expressions) and "closed" Lambda expressions. ……My interest in the environment problem began while Landin, who had a deep understanding of the problem, visited MIT during 1966-67. I then realized the correspondence between the FUNARG lists which are the results of the evaluation of "closed" Lambda expressions in LISP and ISWIM's Lambda Closures.

dead-url = no

}}；故而實現演員的關鍵，是將詞法作用域介入到 Lisp 中。

在 1975 年，基於對 λ 演算表達能力的理解，傑拉德・薩斯曼與蓋伊・史提爾開發出了新型 Lisp 直譯器⑵ explain how to use these control structures, independent of such issues as pattern matching and data base manipulation.⑶ have a simple concrete experimental domain for certain issues of programming semantics and style.}}，並將在其上完成的微縮版的演員實現命名為「Schemer」，後因作業系統檔名的字元數限制而改為 Scheme。儘管 Hewitt 指出了 Scheme 不能表達特定型別的原語演員 |url=https://arxiv.org/abs/0907.3330v24 |year=2009 |quote=In summary, Sussman and Steele 1975 mistakenly concluded “we discovered that the 'Actors' and the lambda expressions were identical in implementation.” The actual situation is that the λ-calculus is capable of expressing some kinds of sequential and parallel control structures but, in general, not the concurrency expressed in the Actor model. On the other hand, the Actor model is capable of expressing everything in the λ-calculus and more Hewitt 2008f. |access-date=2022-12-23 |archive-date=2022-12-23 |archive-url=https://web.archive.org/web/20221223160359/https://arxiv.org/abs/0907.3330v24 |dead-url=no }}，Scheme 直譯器本身採用的簡約的語法和語義，很快贏得廣泛接受。

在 1978 年，蓋伊・史提爾二世和傑拉德・傑伊・薩斯曼發表了《修訂的 Scheme 報告：一種 LISP 方言》，從而完善了 Scheme 的功能，並正式將其確立為 Lisp 的一種主要方言。在 1998 年，二人在總結 Scheme 歷史時指出，簡單而強大的 λ 演算，使得 Scheme 最終得以實現極度的精簡化。

2.2 λ 論文集

「λ 論文集」是傑拉德・薩斯曼與蓋伊・史提爾撰寫的關於 Scheme 的一系列論文，最早作為麻省理工學院的內部備忘錄發表。通常認定 λ 論文集包括：

1975 年: Scheme: An Interpreter for Extended Lambda Calculus.

1976 年: Lambda: The Ultimate Imperative.

1976 年: Lambda: The Ultimate Declarative.

1977 年: Debunking the 'Expensive Procedure Call' Myth, or, Procedure Call Implementations Considered Harmful, or, Lambda: The Ultimate GOTO.

1978 年: The Art of the Interpreter or, the Modularity Complex (Parts Zero, One, and Two).

1978 年: RABBIT: A Compiler for SCHEME.

1979 年: Design of LISP-based Processors, or SCHEME: A Dialect of LISP, or Finite Memories Considered Harmful, or LAMBDA: The Ultimate Opcode.

1980 年: Compiler Optimization Based on Viewing LAMBDA as RENAME + GOTO. AI: An MIT Perspective.

1980 年: Design of a Lisp-based Processor. CACM. 23. 11.

2.3 標準化

Scheme 的業界標準，是由專門的掌控委員會發表的《第 n 次修訂的演演算法語言 Scheme 報告》（Revisedn Report on the Algorithmic Language Scheme），這種標題形式參照了 ALGOL 60 標準文件的標題。Scheme 曾經由 IEEE 標準化為 IEEE 1178–1990，它於 2019 年 11 月 07 日停用。

1998 年透過的 R5RS 是現在被普遍接受的標準。2007 年透過的 R6RS，做出了很大的變動，Scheme 社群中有使用者指責它在堆積華而不實的功能。Scheme 掌控委員會決定在 R7RS 中，將 Scheme 分化為兩個獨立而相容的語言：一個是 2013 年透過的 R7RS-small，它為教學場合提供對 IEEE/R5RS 標準的及時更新，R7RS-small 目前已經有了一些實現支援；而另一個是作為標準庫合集的 R7RS-Large，它包含了各種提議被接納並進入凍結狀態的（SRFI），以此為實際程式設計場合提供對 R6RS 標準的繼續完善，現已發表了有關資料結構部份的 R7RS-Large 過渡草案紅色版。

命名法

在正式場合比如 Scheme 標準的中，提及一個 λ 表示式或原始過程時，偏好使用詞語 “過程” 而非 “函式”。在一般使用中，詞語 “過程” 和 “函式” 是可互換使用的。過程應用有時被正式稱呼為 “組合”（combination）。

同其他 Lisp 一樣，在 Scheme 中使用術語 “”，來提及沒有實際引數的過程。術語 “適當”（proper）尾遞迴，稱謂所有 Scheme 實現的一個性質，它們都進行尾遞迴最佳化，從而支援無限數目的活躍尾遞迴。

基礎特徵

Scheme 大體上是一個函式式程式語言，並支援其他程式設計範型。它同其他 Lisp 程式語言家族語言共享了很多特徵。它的非常簡單的語法基於 Lisp 的 S - 表示式：即圓括號包圍的列表，其中的字首是算符，而隨後那些的是實際引數。故而 Scheme 程式由巢狀的列表的序列構成。列表也是 Scheme 中的主要資料結構，這導致了在原始碼和資料格式之間的緊密等價性，即同像性。Scheme 程式可以輕易的動態建立和求值 Scheme 程式碼片段。

Scheme 與其他 Lisp 方言的列表，都是基於最基礎的資料結構有序對（pair）。Scheme 從它的 Lisp 先輩繼承了一組豐富的列表處理原始運算，比如 cons、和。Scheme 的變數都使用動態強型別系統，Scheme 中的過程都是頭等物件，即頭等函式，因此過程可以作為值賦值給變數，或作為實際引數傳遞給過程。

本章主要集中於語言的創新特徵，它們使得 Scheme 區別於其他 Lisp 方言。除非專門指出，這裡描述的特徵都有關於 R5RS 標準。在本章例子中使用 “===> 結果” 表示法，來指示求值在緊前行上的表示式的結果。這同於 R5RS 中使用的約定。本章節描述的特徵使得 Scheme 不同於在它之前的其他程式語言。Scheme 的這些方面強烈的影響了 Scheme 語言的所有產品，並共通於始自 1975 年最初的 λ 論文，特別是自從 R2RS 以來 Scheme shares with Common Lisp the goal of a core language common to several implementations. Scheme differs from Common Lisp in its emphasis upon simplicity and function over compatibility with older dialects of Lisp.|access-date=2022-11-17|archive-date=2022-10-17|archive-url=https://web.archive.org/web/20221017071206/https://dspace.mit.edu/bitstream/handle/1721.1/5600/AIM-848.pdf|dead-url=no}}，所有版本的 Scheme 程式語言。

4.1 極簡主義

Scheme 的簡約性，使它成為具備同級別功能的程式語言中最易於實現的語言，這得益於使用 λ 演算來從更原始的形式匯出多數的語言語法。例如在 R5RS 標準中，定義了 23 個基於 S - 表示式的語法構造，其中 14 個被歸類為派生形式或庫形式，它們可以被寫為涉及原則上為 lambdad 的更基礎形式的宏。正如 R5RS（§3.1）所說：“最基礎的變數繫結構造是 lambda 表示式，因為所有其他的變數繫結構造，都可以依據 lambda 表示式來做出解釋”。

 基礎形式：define、lambda、quote、if、define-syntax、let-syntax、letrec-syntax、syntax-rules、set!

 派生形式：do、let、let*、letrec、cond、case、and、or、begin、命名 let、delay、unquote、unquote-splicing、quasiquote

下面是一個宏的例子，它將 let 實現為使用 lambda 來進行變數繫結的一個表示式：

(define-syntax let

(syntax-rules

((let ((var expr) ...) body ...)

((lambda (var ...) body ...) expr ...))))

這裡的 (let ((var expr) ...) body ...) 稱為 “模式”，模式中起始的 let 被稱為 “關鍵字”，syntax-rules 的空括號中可添入作為輔助關鍵字的叫做 “文字” 的識別符號，var、expr 和 body 稱為 “模式變數”，... 是稱為 “省略號” 的識別符號，它表示所跟隨的子模式可以匹配零或多個輸入中的元素；而 ((lambda (var ...) body ...) expr ...) 稱為 “模板”。使用上述定義的 let，一個 Scheme 實現可以將 (let ((a 1)(b 2)) (+ b a))，重寫為 ((lambda (a b) (+ b a)) 1 2)，這將實現任務縮減為編碼過程例項化的工作。

4.2 詞法作用域

不像更早期的 Lisp 比如 MaclispLocal variables are more like the variables in commonly-used algebraic programming languages such as Algol or PL/I. A local variable no longer has an associated atomic symbol; thus it can only be referred to from the text of the same function that bound it. The compiler creates local variables for PROG variables, DO variables, and LAMBDA variables, unless directed otherwise. The compiled code stores local variables in machine registers or in locations within a stack.The principal difference between local variables and special variables is in the way a binding of a variable is compiled. (A binding has to be compiled when a PROG, DO, or LAMBDA expression is compiled, and for the entry to a function which has LAMBDA variables to be bound to its arguments.) If the variable to be bound has been declared to be special, the binding is compiled as code to imitate the way the interpreter binds variables: the value of the atomic symbol is saved and a new value is stored into its value cell. If the variable to be bound has not been declared special, the binding is compiled as the declaration of a new local variable. Code is generated to store the value to which the variable is to be bound into the register or stack-location assigned to the new local variable. This runs considerably faster than a special binding.Although a special variable is associated with an atomic symbol which has the name of the variable, the name of a local variable appears only in the input file － in compiled code there is no connection between local variables and atomic symbols. Because this is so, a local variable in one function may not be used as a “free variable” in another function since there is no way for the location of the variable to be communicated between the two functions.When the usage of a variable in a program to be compiled does not conform to these rules, i.e. it is somewhere used as a “free variable,” the variable must be declared special. There are two common cases in which this occurs. One is where a “global” variable is being used, i.e. a variable which is SETQ'ed by many functions but is never bound. The other is where two functions cooperate, one binding a variable and then calling the other one which uses that variable as a free variable.|access-date=2022-12-16|archive-date=2022-12-16|archive-url=https://web.archive.org/web/20221216182701/http://maclisp.info/pitmanual/complr.html#23.1.2|dead-url=no}}，Scheme 像多數現代語言一樣，採用了詞法作用域 Second, the upward Moses requires that the environment structure be potentially tree-like. Finally, the environment at any point in a computation can never be deeper than the lexical depth of the expression being evaluated at that time; i.e., the environment contains bindings only for variables bound in lambdas lexically surrounding the expression being evaluated. This is true even if recursive functions are involved. ……Furthermore, it is not even necessary to scan the environment for the variable, since its value must be in a known position relative to the top of the environment structure; this position can be computed by a compiler at compile time on the basis of lexical scope. }}

：在一個程式單元中所有可能的變數繫結，都可以透過閱讀這個程式單元來分析出來，而不需要考慮到它可能在其中被呼叫的那些上下文。這對比於動態作用域，它是早期 Lisp 方言的特徵，因為在當時的編譯器和直譯器中，用來實現詞法作用域演算法的原始的文字替換方法，關聯著處理代價。在動態作用域的 Lisp 中，對一個過程內的自由變數的引用，依賴於這個呼叫的上下文，完全有可能引用到這個過程外部的相當不同的繫結。

4.3 λ 演算

邱奇的 λ 演算的數學表示法，啟發了 Lisp 使用 lambda 作為介入一個過程的關鍵字，並影響了 Lisp 中涉及到使用高階函式的函數語言程式設計技術的發展。但是早期的 Lisp 由於對自由變數的處理方式

dead-url = no}}

，而不適合表達 λ 演算。

一個正式的 λ 系統，擁有一些公理和一個完備的推理規則。這有助於使用數理邏輯和工具來進行分析。在這個系統中，演算可以被看作直接的演繹。λ 演算的語法，來自用 x, y, z, ...、圓括號、空格、點號和符號 λ 形成的遞迴表示式。λ 演算的功能包括：首先，充當強力的數理邏輯的起點。其次，它可以縮減程式設計者在考慮實現細節上的要求，因為它可以用於模擬機器求值。最後，λ 演算建立了一個堅實的元理論。

在 Scheme 中，lambda 關鍵字被用於定義匿名過程，並且使用 define 基礎形式來定義命名過程，即將一個 lambda 過程繫結到指名的全域性變數。在 Scheme 中，(define (foo x) (+ x 1)) 與 (define foo (lambda (x) (+ x 1)))

，在語法上是等同的。例如有序對可以表示為：

(define (cons x y)

(lambda (m) (m x y)))

(define (car z)

(z (lambda (p q) p)))

(define (cdr z)

(z (lambda (p q) q)))

這樣定義出來的 cons、car 和 cdr，滿足恆等式 (car (cons x y)) 等於 x，和 (cdr (cons x y)) 等於 y。甚至遞迴也可以表示為利用 λ 演算的 Y 組合子。

詞法作用域的介入，透過在某些形式的 λ 表示法，和在工作程式語言中它們的實際表達之間做出等價，解決了早期 Lisp 的有關問題。Sussman 和 Steele 展示了，透過將 λ 表示式用作 “控制結構和環境修改器”，而非簡單的過程例項化，可以用這個新語言優雅的匯出其他程式語言包括 ALGOL 和 Fortran 的，所有指令式和宣告式語義，和其他 Lisp 的動態作用域。他們在第一篇 λ 論文中，與對 Scheme 的首次描述一起，介入了，並在後續的論文中，他們推進演示了在這種實際使用中體現出的 λ 演算的原生能力。

4.4 過程應用中的求值次序

Scheme 採用了傳值呼叫，但不同於多數 Lisp 規定了過程實際引數的求值次序，Scheme 不規定求值次序。對比於其他 Lisp，Scheme 表示式在算符位置（第一個專案）上可以是一個表示式，只要在算符位置上的表示式的結果是一個過程，這種表示就是完全合法的。

在 Scheme 中，在算符和實際引數位置上的這些表示式的求值次序，可以在逐個呼叫的基礎上由實現來選擇，而唯一的約束是：“運算子和運算數表示式的任何併發求值的效果，被約束為一致於某種順序次序的求值。”（R5RS sec. 4.1.3）

(let

((ev (lambda (n)

(display "Evaluating")

(display (if (procedure? n) "procedure" n))

(newline)

n)))

((ev +) (ev 1) (ev 2)))

ev 是一個過程，它描述傳遞給它的實際引數，接著返回這個實際引數的值。在呼叫過程 + 來加 1 和 2 之中，表示式 (ev +)、(ev 1) 和 (ev 2) 可以按任何次序求值，只要效果上它們不像是並行求值的。因此在上述例子被執行的時候，標準 Scheme 可以按任何次序來顯示前三行：

Evaluating procedure

Evaluating 1

Evaluating 2

3

然而一行的文字不可以同另一行的文字產生交錯，因為這會違反順序求值約束。

4.5 塊結構

Scheme 的程式碼塊結構，不同於早先 Lisp 的語言構造。

自從 R2RS 開始，Scheme 中的塊，是透過三個 “繫結構造” 來實現的：、let * 和 letrec。例如，下列構造建立一個塊，其中叫做 var 的符號被繫結到數 10：

(define var "goose")

; 這裡任何到 var 的引用都會被繫結到 "goose"

(let ((var 10))

;; 語句走到這裡時，任何到 var 的引用都會繫結到 10

)

; 這裡任何到 var 的引用都會繫結到 "goose"

依據程式設計者的需要，塊可以來建立任意複雜的塊結構。使用塊結構來建立區域性繫結，減輕了不然可能發生的。

let 的變體之一 let*，允許繫結引用在前面相同構造中定義的變數，例如：

(let ((var1 10)

(var2 (+ var1 12)))

;; 但是 var1 的定義不可以引用 var2

)

let 的另一個變體 letrec，被設計用來確使互遞迴過程可繫結彼此。

下面的例子是侯世達：

; 將侯世達雌雄序列計算為有序對的列表

(define (hofstadter-male-female n)

(letrec

((female (lambda (n)

	 (if (= n 0)

	 1

		(- n (male (female (- n 1)))))))

	 (male (lambda (n)

	 (if (= n 0)

		0

		(- n (female (male (- n 1))))))))

(let loop ((i 0))

(if (> i n)

	 '

	 (cons (cons (female i) (male i))

		 (loop (+ i 1)))))))

(hofstadter-male-female 8)

4.6

在一個單一的 letrec 中的所有過程，可以透過名字引用其他的過程，還有在相同的 letrec 中此前定義的變數的值，但是它們不可以引用在相同的 letrec 中此後定義的變數的值。

let 的變體 “命名 let” 形式，在 let 關鍵字之後有一個識別符號。它將這些 let 變數繫結到一個過程的實際引數，這個過程的名字由這個識別符號給出，它的過程體是 let 形式的主體。這個過程體可以透過呼叫這個過程達成想要的重複。命名 let 被廣泛用於實現迭代。

一個簡單的計數器例子:

(let loop ((n 1))

(if (> n 10)

'

(cons n

	 (loop (+ n 1)))))

4.7

就像 Scheme 中的任何過程一樣，在命名 let 中建立的這個過程是頭等物件。

4.8 適當尾遞迴

Scheme 擁有一個迭代構造 do，但是在 Scheme 中更推崇的，是使用尾遞迴來表達迭代。遵循標準的 Scheme 實現被要求最佳化尾遞迴，從而支援無限數量的活躍尾遞迴（R5RS sec. 3.5），這個性質被 Scheme 報告描述為 “適當”（proper）尾遞迴，它使得 Scheme 程式設計者可以安全的使用遞迴結構來書寫迭代演算法，這在很多時候是更符合直覺的。尾遞迴過程和命名 let 形式，提供對使用尾遞迴的迭代的支援。

; 建造從 0 到 9 的平方的列表：

; 注意：loop 簡單的就是用作標籤的一個任意符號。任何符號都行。

(define (list-of-squares n)

(let loop ((i n) (res '))

(if ( (0 1 4 9 16 25 36 49 64 81)

4.9 頭等續體

在 Scheme 中續體是頭等物件。自從 R2RS 開始，Scheme 提供了過程

，簡寫為 call/cc，它捕獲當前續體，將其包裝成逃脫（escape）過程，再繫結到程式設計者提供的一個過程的唯一形式引數上。如果逃脫過程在後來某個時候被呼叫，它會放棄在後來時候正生效的任何續體，轉而使用在建立這個逃脫過程之時生效的那個續體。逃脫過程與原本呼叫 call/cc 的續體，接受相同數目的實際引數；除了用 call-with-values 建立的續體，所有續體恰好接受一個值（R5RS sec. 6.4）。

頭等續體使得程式設計者能夠建立非區域性控制結構比如迭代器、協程和回溯。續體可以被用來模擬指令式程式語言中 return 語句的行為。下列函式 find-first 接受函式 func 和列表 lst，返回 lst 中的第一個元素 x，它使得 (func x) 返回真。

(define (find-first func lst)

(call/cc

(lambda (return)

(for-each

(lambda (x)

(if (func x)

	 (return x)))

lst)

f)))

(find-first integer? '(1/2 3/4 5.6 7 8/9 10 11))

4.10

(find-first zero? '(1 2 3 4))

4.11

David Madore 在 1999 年提出的陰陽謎題，展示了 Scheme 可以將續體當作頭等物件處理，繫結它們到變數，和把它們作為給過程的實際引數來傳遞。

4.12 統一的名稱空間

對比於 Common Lisp，在 Scheme 中所有的資料和過程共享一個共同的名稱空間，而 Common Lisp 中有函式和資料分離的名稱空間，使得一個函式和一個變數可以有相同的名字，並且將一個函式作為值引用時要求特殊的表示法。這有時叫做 “Lisp1 與 Lisp2” 差異，二者分別稱謂 Scheme 的統一的名稱空間，和 Common Lisp 的分立的名稱空間。

在 Scheme 中， 可以使用操縱和繫結資料的原始運算來繫結過程。沒有等價於 Common Lisp 的 defun 和 #' 的原始運算。

; 變數繫結到一個數：

(define f 10)

f

4.13

; 變化（改變繫結值）

(set! f (+ f f 6))

f

4.14

; 將一個過程賦值給相同的變數：

(set! f (lambda (n) (+ n 12)))

(f 6)

4.15

; 將一個表示式的結果賦值給相同的變數：

(set! f (f 1))

f

4.16

; 函數語言程式設計：

(apply + '(1 2 3 4 5 6))

4.17

(set! f (lambda (n) (+ n 100)))

(map f '(1 2 3))

4.18

實現標準

本章歸檔多年來做出的給與 Scheme 特定特徵的設計決定，它們不是最初設計的直接產物。

5.1 註釋

直到 R5RS 標準，在 Scheme 中的標準註釋是分號，它使得這行餘下部份對 Scheme 不可見。許多實現支援可替代的約定，允許註釋擴充套件為多於一行，而 R6RS 標準允許其中的兩種：一個完整的 S - 表示式，可以透過前導 而變成一個註釋（介入於 SRFI 62），和透過用 | 和 |# 圍繞文字，產生的 “多行註釋” 或 “塊註釋”。

5.2 在布林表示式中非布林值的處理

在多數 Lisp 方言包括 Common Lisp 中，布林表示式中的值 NIL 按照慣例被求值為值假。在 Scheme 中，自從 1991 年的 IEEE 標準，除了 #f 的所有的值，包括在 Scheme 中寫為 ' 的 NIL 的等價者，在布林表示式中求值為值真（R5RS sec. 6.3.1）。

在多數 Lisp 中表示布林值真的常量是 T，而在 Scheme 中它是 #t。

5.3 乾淨宏

Scheme 的語法可以輕易的透過宏系統來擴充套件。R5RS 標準介入了強力的乾淨宏系統，它允許程式設計者使用一種簡單的模式匹配子語言，向語言增加的新的語法構造（R5RS sec 4.3）。在此前的 R4RS 標準中，乾淨宏系統已經作為 “高階” 系統，和 “低階” 宏系統一起被編排入標準的附錄中，二者都被當作對 Scheme 的擴充套件而非語言的本質部份。

乾淨宏的實現，也叫做 syntax-rules, 被要求遵守語言的其餘部份的詞法作用域。這是透過針對宏展開的特殊命名和作用域規則來確保的，從而避免在其他程式語言的宏系統中可能出現的常見程式設計錯誤。R6RS 規定了更加複雜的變換系統 syntax-case，它已經作為對 R5RS Scheme 的一個語言擴充套件而能夠獲得到有一段時間了。例如：

; 定義一個宏來實現 “if” 的具有多個表示式的變體

; 有真分支而無假分支

(define-syntax when

(syntax-rules

((when pred exp exps ...)

(if pred (begin exp exps ...)))))

宏和過程的呼叫看起來非常相似，二者都是 S - 表示式，但是它們被不同的對待。在編譯器遇到程式中的一個 S - 表示式的時候，它首先檢視這個符號是否被定義為在當前詞法範圍內的語法關鍵字。如果是這樣，它接著嘗試展開這個宏，將在這個 S - 表示式尾部的專案當作實際引數，而不用編譯程式碼來求值它們，遞迴的重複這個處理過程直到沒有餘留的宏呼叫。如果它不是一個語法關鍵字，編譯器編譯程式碼來求值在這個 S - 表示式尾部的實際引數，並接著求值在這個 S - 表示式頭部的符號所表示的變數，把它作為過程來呼叫，並把最終的尾部表示式作為實際引數傳遞給它。

多數 Scheme 實現還提供額外的宏系統。其中最流行是語法閉包、顯式重新命名宏和 define-macro，它是類似於 Common Lisp 中提供的 defmacro 系統的非乾淨宏。

不能指定一個宏是否為乾淨的，是宏系統的一個缺點。可作為替代的展開模型比如作用域集合，提供一種潛在的解決方案。

5.4 延遲求值

自從 R2RS 開始，Scheme 透過 delay 形式和過程 force 支援延遲求值。

(define a 10)

(define eval-aplus2 (delay (+ a 2)))

(set! a 20)

(force eval-aplus2)

5.5

(define eval-aplus50 (delay (+ a 50)))

(let ((a 8))

(force eval-aplus50))

5.6

(set! a 100)

(force eval-aplus2)

5.7

delay 原始運算，產生叫做 promise 的一個物件，它在將來的某個時間點上被詢問從而遞交結果值，promise 的原本定義的文字內容會被留存，並且在第一次使用 force 之後它的值也會被留存。promise 永遠只求值一次。它們可以被用來實現高階的惰性求值構造比如串流。

在 R5RS 中，給出了 delay 和 force 的推薦實現，將 promise 實現為沒有實際引數的一個過程，並使用記憶化來確保它永遠只求值一次，與呼叫 force 的次數無關（R5RS sec. 6.4）。在 R6RS 標準中，它們不再是原始運算，轉而作為 R5RS 相容庫的一部份提供（rnrs r5rs (6)）。

SRFI 41 確使表達有限和無限序列能夠具有非凡的經濟性。例如，這是使用在 SRFI 41 中的函式定義的一個斐波那契數列：

; 定義斐波那契序列

(define fibs

(stream-cons 0

(stream-cons 1

(stream-map +

fibs

(stream-cdr fibs)))))

; 計算這個序列的第一百個數

(stream-ref fibs 99)

5.8

5.9 eval 及其執行環境

R5RS 標準之前，在其他 Lisp 中無處不在的 eval 過程，在 Scheme 中沒有等價者。在第一篇 λ 論文中，曾經將 evaluate 描述為 “類似於 LISP 函式 EVAL”，但在具有詞法作用域的 Scheme 中，對這個表示式在哪個環境中求值存在困惑。例如，不明確求值下列表達式的結果應當是 5 還是 6：

(let ((name '+))

(let ((+ *))

(evaluate (list name 2 3))))

在求值實際引數 name 的時候，在外層環境中找到了它的定義；在求值結果表示式 (+ 2 3) 的時候，如果在外層環境中求值，結果是兩個運算數的總和；如果在內層環境中求值，這裡符號 + 已經被繫結到過程 * 的值，結果是兩個運算數的乘積。為此在 1978 年的最初修訂報告中，將 evaluate 替代為 enclose，它接受分別為程式碼和執行環境的兩個實際引數。由於各種技術和實際原因，第二次、第三次和第四次修訂報告省略了任何 eval 的等價者。

R5RS 透過規定三個返回環境的過程，並提供接受一個 S - 表示式和一個環境的過程 eval，它在提供的這個環境內求值這個表示式（R5RS sec. 6.5），解決了這個困惑。R6RS 透過提供叫做 environment 的一個過程進行了擴充套件，程式設計者透過它可以精確的指定將哪個物件匯入到求值環境之內。

透過現代 Scheme（通常兼容於 R5RS）來求值表示式 expr，你需要定義如下這樣的一個函式 evaluate：

(define (evaluate expr)

(eval expr (interaction-environment)))

interaction-environment 是直譯器的全域性環境。

5.10 數值塔

}}

Scheme 規定了數值資料型別的相較完全的集合，包括複數和有理數型別，這在 Scheme 中叫做 “數值塔”（R5RS sec. 6.2）。標準對它們作抽象處理，而不委以任何特定的內部表示。

數可以有精確性品質。一個精確數只能從涉及其他精確數的精確運算產生，不精確是傳染的。標準規定任何兩個實現對產生精確數的所有運算都必須產生等價的結果。

R5RS 標準規定了過程 exact->inexact 和 inexact->exact，它們可以用於改變一個數的精確性。inexact->exact 產生 “在數值上最接近實際引數的精確數”。exact->inexact 產生 “在數值上最接近實際引數的不精確數”。R6RS 標準在主報告中省略了這些過程，而是在標準庫中將它們規定為 R5RS 相容過程（rnrs r5rs (6)）。

在 R5RS 標準中，不要求 Scheme 實現去實現整個數值塔，而是它們必須實現 “符合實現用途和 Scheme 語言精神二者的連貫子集”（R5RS sec. 6.2.3）。新的 R6RS 標準要求實現整個數值塔，並且 “精確整數物件和精確有理數物件實際上有無限的大小和精度，並且對於實現特定過程... 所以它們在得到精確實際引數時總是返回精確結果”（R6RS sec. 3.4, sec. 11.7.1）。

在支援精確有理複數的實現中的精確算術例子：

; 三個有理實數和兩個有理複數的總和

(define x (+ 1/3 1/4 -1/5 -1/3i 405/50+2/3i))

x

5.11

; 檢查精確性

(exact? x)

5.12

在既不支援精確有理數也不支援複數卻接受有理數表示法的實數的實現中相同算術的例子：

; 四個有理實數的總和

(define xr (+ 1/3 1/4 -1/5 405/50))

; 兩個有理實數的總和

(define xi (+ -1/3 2/3))

xr

5.13

xi

5.14

; 檢查精確性

(exact? xr)

5.15

(exact? xi)

5.16

二者實現都符合 R5RS 標準，但是第二個實現不符合 R6RS，因為它沒有實現完整的數值塔。

5.17 原始資料型別不相交

在 Scheme 中原始資料型別是不相交的。對於任何 Scheme 物件，下列謂詞中只有一個可以為真：boolean?、pair?、symbol?、number?、char?、>string?、vector?、port? 和 procedure?（R5RS sec 3.2）。

作為對比，在數值資料型別之內，對數值值是可以有交疊的。例如，一個整數值可以同時滿足如下謂詞：integer?、rational?、real?、complex? 和 number?（R5RS sec 6.2）。

5.18 等價謂詞

Scheme 在任意物件之間有三種不同型別的等價，它們透過三種不同的 “等價謂詞” 來指示，即測試等式的關係算符 eq?、eqv? 和 equal?：

eq? 求值為 #f，除非它的實際引數表示在記憶體中相同的物件；
eqv? 大體上同於 eq?，但是特殊處理原始物件（比如字元和數），使得表示相同值的數 eqv? 為真，即使它們不引用相同的物件；
equal? 比較資料結構比如列表、向量和字串，來確定它們是否有全等的結構並且其內容 eqv? 為真（R5RS sec. 6.1）。

在 Scheme 中還存在依賴於型別的等價運算：string=? 和 string-ci=? 比較兩個字串（後者進行不依賴大小寫比較）；char=? 和 char-ci=? 比較字元；= 比較數。

5.19 輸入 / 輸出

Scheme 的輸入和輸出基於了 “埠” 資料型別（R5RS sec 6.6）。R5RS 定義了兩個預設埠，可透過過程 current-input-port 和 current-output-port 來訪問，它們對應於 Unix 概念的標準輸入和標準輸出。多數實現還提供 current-error-port。標準透過標準過程比如 with-input-from-file 和 with-output-to-file，支援輸入和標準輸出的重定向。多數實現提供有重定向能力的字串埠，使用在 SRFI 6 中描述的過程，確使很多常規的輸入 - 輸出運算能在字串緩衝區上進行而非在檔案上。R6RS 標準規定了更多複雜和有能力的埠過程和很多新的埠型別。

下面的例子是使用嚴格的 R5RS Scheme 書寫的。

例子 1，預設輸出到 current-output-port：

(let ((hello0 (lambda (display "Hello world") (newline))))

(hello0))

例子 2，類似例子 1 但對輸出過程使用可選的埠引數的例子：

(let ((hello1 (lambda (p) (display "Hello world" p) (newline p))))

(hello1 (current-output-port)))

類似例子 1，但是輸出被重定向到一個新建檔案：

; NB: with-output-to-file is an optional procedure in R5RS

(let ((hello0 (lambda (display "Hello world") (newline))))

(with-output-to-file "helloworldoutputfile" hello0))

類似例子 2，但是具有顯式的檔案開啟和埠關閉來發送輸出到檔案：

(let ((hello1 (lambda (p) (display "Hello world" p) (newline p)))

(output-port (open-output-file "helloworldoutputfile")))

(hello1 output-port)

(close-output-port output-port))

類似例子 2，但是使用 call-with-output-file 來發送輸出到一個檔案：

(let ((hello1 (lambda (p) (display "Hello world" p) (newline p))))

(call-with-output-file "helloworldoutputfile" hello1))

對輸入提供了類似的過程。R5RS Scheme 提供了謂詞 input-port? 和 output-port?。對於字元輸入和輸出提供了 write-char、read-char、peek-char 和 char-ready?。為了書寫和閱讀 Scheme 表示式，Scheme 提供了 read 和 write。在讀運算時，如果輸入埠到達了檔案的結束處，則返回的結果是 end-of-file 物件，並且這可以使用謂詞 eof-object? 來測試。

除了標準之外，SRFI 28 定義了一個基本的格式化過程，類似 Common Lisp 的 format 並以此命名。

5.20 標準過程的重定義

在 Scheme 中，過程被繫結到變數。在 R5RS 中語言標準正式的授權程式設計者可以變更內建過程的變數繫結，在效果上重定義它們（R5RS "Language changes"）。例如，透過重定義 + 可以將它擴充套件為同接受數一樣接受字串：

(set! +

(let ((original+ +))

(lambda args

(apply

(if (or (null? args) (string? (car args)))

string-append

original+)

args))))

(+ 1 2 3)

5.21

(+ "1" "2" "3")

5.22

在 R6RS 中所有繫結，包括標準過程，都屬於某個庫，並且所有匯出的繫結都是不可變的（R6RS sec 7.1）。因此，禁止透過變更進行標準過程的重定義。轉而，可以在標準過程的名字下匯入不同的過程，這在效果上類似於重定義。

標準形式和過程概述

Scheme 語言正式定義於 1998 年的 R5RS 和 2007 年 R6RS 標準之中。它們描述了標準 “形式”，即關鍵字和隨同的語法，它們提供語言的控制結構，和執行通常任務的標準過程。

在標準 Scheme 中，從一個數據型別轉換到另一個數據型別的過程，在它們的名字中包含字串 ->，謂詞結束於一個？，而改變已經分配了資料的值的過程結束於一個！。Scheme 程式設計者通常跟從這些命名約定。

6.1 標準形式

本表格描述 Scheme 中的標準形式。某些形式出現在不止一行之中，因為它們不能被輕易的歸類入語言中的一個單一功能。

在表格中標記了 “L” 的形式被歸類為標準中的派生的 “庫” 形式，並且在實踐中通常被實現為使用了更基礎形式的宏，這使得實現任務比在其他語言中要容易許多。

注意 begin 在 R5RS 中被定義為庫語法，但是展開者需要知道它來完成分片功能。在 R6RS 中它不再是庫語法。

6.2 標準過程

下面兩個表格描述在 R5RS Scheme 中的標準過程。R6RS 更加具有可擴充套件性而如此總結是不實際的。

某些過程出現在不止一行之中，因為它們不能輕易的分類入語言的一個單一功能。

在其名字中包含 - ci 的字串和字元過程，在它們的實際引數之間進行不依賴大小寫的比較：相同字元的大寫和小寫版本被認為是相等的。

接受多於二個實際引數的 - 和 / 在 R5RS 中被定義了但留作為可選的。

Scheme 實現要求

}}

由於 Scheme 的極簡主義，很多常見過程和語法形式不是由標準定義的。 為了保持核心語言很小並促進擴充套件的標準化，Scheme 社群擁有 “Scheme 實現要求”（SRFI）流程，透過對擴充套件提案的仔細研討來定義擴充套件庫。這提升了程式碼可移植性。很多 SRFI 被幾乎所有 Scheme 實現支援。

在不同的實現中具有相當廣泛支援的 SRFI 包括：

0: 基於特徵的條件展開構造
1: 列表庫
4: 同質數值向量資料型別
6: 基本字串埠
8: 接收、繫結到多個值
9: 定義記錄型別
13: 字串庫
14: 字元集庫
16: 可變元數過程的語法
17: 廣義 set!

18: 多執行緒支援
19: 時間資料型別和過程
25: 多維陣列原語
26: 不用柯里化的特殊化形式引數的表示法
27: 隨機數位的來源
28: 基本格式化字串
29: 本地化
30: 巢狀的多行註釋
31: 遞迴求值的特殊形式
37: args-fold：程式實際引數處理器
39: 形式引數物件
41: 串流
42: 及早推導式
43: 向量庫
45: 表達迭代式惰性演算法的原語
60: 作為位元的整數
61: 更一般性的 cond 子句
66: 八位組向量
67: 比較過程

實作

Scheme 的精簡設計，使得程式語言設計人士與愛好者，特別鍾愛研究它，故而它有不斷湧現的新實作，而活躍開發的實作也在持續跟進語言標準更新。儘管 Scheme 有眾多實現是它的一個主要長處，但是由於 Scheme 沒有庫函式標準，造成了很多不可互通的實作互相競爭，試圖使用 Scheme 編寫既複雜又便於移植的程式，往往比較困難。R6RS 和 R7RS-Large，在核心語言之外製定了庫函式標準，使得編譯器開發者和貢獻者可以實現 Scheme 的可移植庫。

幾乎所有 Scheme 實作都有傳承自 Lisp 的「讀取﹣求值﹣輸出迴圈」互動模式，一些 Scheme 實作亦可作為編譯器。很多用 C 語言及衍生語言寫成的軟體，都利用 Scheme 作為指令碼語言，很多嵌入式系統語言即是基於 Scheme。Chez Scheme 和 Larceny 等實現，可以將 Scheme 程式編譯為本機二進位制碼。Gambit 和 Chicken 等實現，可將 Scheme 程式編譯為 C 程式碼，還能編譯成 JVM 位元組碼或者.Net 位元組碼。

一些實現支援額外的特徵。比如提供與 Java 類的整合，而 Scheme 到 C 編譯器通常易於使用 C 寫成的外部庫，甚至允許將實際 C 程式碼嵌入到 Scheme 原始碼中。另一個例子是用 java 書寫的，它提供了支援 Scheme 學習的一組可視工具。

教科書

《計算機程式的構造和解釋》（SICP），由 Scheme 創始人之一傑拉德・薩斯曼與哈爾・阿伯爾森編寫，它是最著名的使用 Scheme 語言的電腦科學教科書。書中用 Scheme 自身實現了 Scheme 的自迴圈直譯器，其簡單的語法可以幫助使用者理解 Scheme 的執行過程。
《程式設計方法》，由馬蒂亞斯・費萊森等人編寫，它對 SICP 中的一些被認為過於艱澀的概念進行了改進。
《》，由丹尼爾・福瑞得曼等人編寫，它是以 Scheme 為基礎的教科書。
《簡單的 Scheme：介紹計算機科學》，由柏克萊加州大學資深講師布萊恩・哈維編寫，它是一本專為中學級別，無電腦科學基礎的學生編寫的入門書。

實際用處

很多著名的電腦科學院校都利用 Scheme 來教授入門級課程。以下為一些最為著名的教授 Scheme 的學校：

麻省理工學院是 Scheme 與 SICP 的誕生地。直到 2008 年為止，麻省理工學院的入門課程 6.001 即是用 Scheme 來教授的。儘管現在 Scheme 已經不再被用於入門課程，麻省理工學院到目前為止還在教授 SICP。
柏克萊加州大學的入門課程 61A 到 2010 年為止利用 Scheme 與 SICP 教授入門課程，並利用 Scheme 來實作 Logo，另一個基於 Lisp 的程式語言。自 2011 年起，61A 改用 Python 來教授 SICP。
西北大學的入門課程 CS2500 利用 Scheme 來教授另一本著名的教材《程式設計方法》。
印第安那大學的入門課程 C211 利用 Scheme 來教授。
耶魯大學
萊斯大學
香港科技大學
北京大學
專案在美國超過 600 所高中教授 Scheme 語言。
滑鐵盧大學數學系（包括計算機科學系）的入門課程 CS115、CS116、CS135 利用 Scheme 來教授。
雲林科技大學

自由軟體影像處理程式 GIMP 利用 Scheme 為嵌入式指令碼語言。GNOME 中有到核心庫的一個 GNU 的擴充套件語言 Guile 包裝器。在 2012 年出現的 Julia 所採用的語法解析器，是用 Scheme 方言 Femtolisp 實現的。