陈颂光
全栈工程师,能够独立开发从解释器到网站和桌面/移动端应用的各类软件。
关注我的 GitHub

Scheme概览

Scheme语言是一种追求一致性的语言,拥有极简的语法,精晰的语义。Scheme语言和其它Lisp家族语言一样最初被用于人工智能领域,自1975年由Guy Lewis Steele Jr.与Gerald Jay Sussman发明以来,除用作教学和研究外,还被用作扩展语言(类似Microsoft Office中的宏),GNU项目正打算用Scheme作为各子项目的扩展语言并为此开发了一个解释器guile。Harold Abelson, Gerald Jay, Julie Sussman的《计算机程序的构造和解释》(Structure and Interpretation of Computer Programs)长期在许多家大学被作为本科生第一门课的教材,低起点但深刻而全面,是一切编程教材的典范,极力推荐所有读者读读这本书。我仍然坚持,如果没有要立即投入工作的迫切性,Scheme是第一门语言的首选,因为它简单易学,可以集中于程序设计本身而不被次要的细节干扰。关于scheme的更多资源见http://www.schemers.org/,可找到各种标准、教材、软件等等。Scheme也有一些方言,以下我们介绍R7RS小语言标准。

实现

http://www.schemers.org/中介绍了scheme的许多种实现,其中Racket在教学中比较方便,用在JVM上的话推荐GNU Kawa,在类unix系统上可能装了guile。

特点

  • Scheme采用Lisp的前缀表达式语法。 Scheme可以做到程序即数据的一致性,轻松实现元编程。
  • Scheme是一种静态作用域的语言。 变量的每次使用关联一个词法决定的绑定。
  • Scheme是动态类型语言。 类型与对象(又称值)而不是变量关联。就如Python、Ruby、Smalltalk和其它Lisp方言。
  • 所有在Scheme计算过程中创建的对象,包括过程和延续,有无限的生存期。 可视为没有任何Scheme对象被销毀。Scheme实现(通常)不用光存储空间的原因是它们被允许回收可以证明不可能影响任何未来计算的对象占用的存储空间(自动垃圾回收)。类似C#、Java、Haskell、大多数的Lisp方言、ML、Python、Ruby、Smalltalk。
  • Scheme的实现必须正确地尾递归。 这容许在常数空间中执行迭代计算,即使迭代计算的语法描述为递归过程。因此,用正确的尾部递归实现,可以用自调用机制来表示迭代,因此,特殊的迭代构造只是有用的语法糖。部分其它语言的编译器也进行尾递归优化。
  • Scheme是第一种将过程作为一等公民对象的语言之一。 程序可以动态创建,存储在数据结构中,作为返回值等等。类似Common Lisp、Haskell、ML、Ruby和Smalltalk。
  • Scheme的一个标志性特性为有一等地位的继续。 头等继续对实现广泛的高级控制结构有用,包括非本地退出、回溯和协程,它在大多数语言只在背后工作。
  • Scheme中参数在过程取得控制前求值,不管过程是否需要它们的值。 除了Haskell的惰性求值和Algol 60的按名调用语义外(它们在过程需要时才求值参数表达式)与大部分语言一样。
  • Scheme的算术模型提供丰富的数值类型和操作。 区分精确和非精确数:一个精确数精确对应于一个数,而非精确数为涉及舍入或其它近似的数。
  • Scheme提供健康的宏系统 Scheme的宏展开仍维持词法作用域,从而避开C语言之类的基于文本替换的宏系统常有的陷阱。

快速概览

表达式

Scheme代码大部分主要元素为表达式。表达式可被求值, 提供值 (实际上任意多个值)。最基本的表达式为字面值表达式:

#t ;的值为 #t
23 ;的值为 23

这记号指 #t 求值为#t,即‘真’的值,而23 求值为表示23的数。

复合表达式由括号和子表达式组成。第一个子表达式确定操作; 其余子表达式为操作数:

(+ 23 42) ;的值为 65
(+ 14 (* 23 42)) ;的值为 980

在第一个例子,+ 是一个内置加法操作的名字,23 和 42 为操作数。表达式 (+ 23 42) 读作 ‘23 和 42的和’。复合表达式可嵌套—第二个表达式读作 ‘14 和 23 与 42 的积的和’。

如这些例子指出的,Scheme的复合表达式都用前缀记法。结果,需要括号指示结构。于是, 在数学和其它编程语言中常见的省略括号在Scheme是不容许的。

和很多其它语言一样,空白(包括行结束符)在分隔子表达式时不重要,可用于显示结构。

变量和绑定

Scheme容许标识符表示包含值的位置。这些标识符称为变量。在很多情况,特别是位置在创建后没有修改过时,把变量直接想成其值是有用的。

(let ((x 23)
      (y 42))
  (+ x y)) ;的值为 65

在本例,let 开始的表达式为一个绑定构造。 let表达式把x 绑定到 23,把y 绑定到 42。这些绑定在且仅在let的体可见。

定义

用 let 表达式绑定的变量是本地的,因为绑定仅在let的体可见。Scheme也容许创建顶层绑定如下:

(define x 23)
(define y 42)
(+ x y) ;的值为 65

(它们实际上在顶层程序或库``项层’。)

前两个括住的结构为定义; 它们创建顶层绑定,绑定x 到 23 和 y 到 42。定义不是表达式,不能在所有容许表达式的地方出现。并且定义没有值。

绑定服从程序的词法结构:当多个绑定同名,变量指向最近的绑定,由内而外:

(define x 23)
(define y 42)
(let ((y 43))
  (+ x y)) ;的值为 66

(let ((y 43))
  (let ((y 44))
    (+ x y))) ;的值为 67

过程

定义也可以用于定义过程:

(define (f x)
  (+ x 42))

(f 23) ;的值为 65

简单地说,过程是表达式抽象成的对象。在例子中,第一个定义定义了一个叫f的过程。 (包围f x的括号表示这是过程定义。) 表达式 (f 23) 为一个过程调用,大致‘求值 (+ x 42) (过程体) ,其中x 绑定到 23’.

过程为对象,可传给其它过程:

(define (f x)
  (+ x 42))

(define (g p x)
  (p x))

(g f 23) ;的值为 65

在例子中,g的体中 p绑定到f 而 x 绑定到 23来求值,相当于(f 23),求值为 65.

事实上,Scheme许多内置操作通过值为过程的变量而非语法提供。例如+操作在很多其它语言作特殊语法提供,在Scheme中只是绑定到一个求和过程的常规标识符。*等亦然:

(define (h op x y)
  (op x y))

(h + 23 42) ;的值为 65
(h * 23 42) ;的值为 966

过程定义并非创建过程的惟一方法。lambda 表达式创建一个新的过程对象,不用给名字:

((lambda (x) (+ x 42)) 23) ;的值为 65

整个表达式为函数调用; (lambda (x) (+ x 42))求值为一个单参过程,把42加到上面。

过程调用和语法关键字

(+ 23 42), (f 23)和 ((lambda (x) (+ x 42)) 23) 都是过程调用的例子,lambda 和 let 表达式不是。因为 let虽是标识符符但不是变量,而是语法关键字。首个子表达式为语法关键字的列表式服从该关键字指定的特殊规则。定义中的define标识符也是语法关键字。 所以,定义也不是过程调用。

lambda 关键字指定首个子列表为一个参数列表,余下子列表为过程体。在 let 表达式,首个子列表为绑定规范,余下子列表构成表达式体。

过程调用和这些表达式类型区别在于列表的首个位置:它不是语法关键字的话表达式就是过程调用。Scheme的语法关键字集较少,这使此工作容易。但可以新建语法关键字绑定。

赋值

由定义、let 或 lambda表达式绑定的Scheme变量不是直接绑定到指定对象,而是绑定到含该对象的位置。这些位置的内容以后可用赋值破坏性地修改:

(let ((x 23))
  (set! x 42)
  x) ;的值为 42

在此情况,let 表达式体有两个表达式,它们顺序求值,最后一个表达式的值为整个 let表达式的值。(set! x 42) 是一个赋值,它说``把x 指向对象42的位置’。从而上一表达式中x值从 23 被改成 42.

派生语法和宏

很多表达式可重写为更基本的表达式类型。例如let 表达式可改写为一个过程调用和一个lambda表达式。以下两个表达式等价:

(let ((x 23)
      (y 42))
  (+ x y)) ;的值为 65

((lambda (x y) (+ x y)) 23 42) ;的值为 65

语法表达式如let表达式称为派生的,因为它们的语义可以通过语法变换从其它表达式得到。一些过程定义也是派生表达式。以下两个定义等价:

(define (f x)
  (+ x 42))

(define f
  (lambda (x)
    (+ x 42)))

Scheme程序可以绑定语法关键字到宏来创建自己的派生表达式:

(define-syntax def
  (syntax-rules ()
    ((def f (p ...) body)
     (define (f p ...)
       body))))

(def f (x)
  (+ x 42))

define-syntax 构造指出括住的结构匹配(def f (p …) body),其中f, p和 body 为模板变量,会被转换为(define (f p …) body)。从而,例子中def 表达式被重写为:

(define (f x)
  (+ x 42))

新建语法关键字的能力使Scheme极富灵活性和表达力,使其它语言中很多特性可直接在Scheme中实现:任何Scheme程序员可新增表达式类型。

语法数据和数据值

数据值 为Scheme对象的子集。包括布尔值、整数、字符、符号、字符串、列表、向量、位向量均为数据值的元素。数据值可以用文本表示为语法数据,可读写而不丢信息。一般地多个语法数据对应一个数据值。进一步,每个数据值在程序中可平凡地写成字面值表达式,只用在前面加' :

'23 ;的值为 23
'#t ;的值为 #t
'foo ;的值为 foo
'(1 2 3) ;的值为 (1 2 3)
'#(1 2 3) ;的值为 #(1 2 3)

上例中'对于符号和列表外字面常量不需要。语法值foo表示名为‘foo’的符号,’foo为以该符号为值的字面表达式。语法值(1 2 3)表示一个有元素1、2、3的列表,’(1 2 3) 为以该列表为值的字面表达式。类似地, 语法值#(1 2 3)表示一个有元素1、2、3的列表#(1 2 3),而’#(1 2 3)为对应字面表达式。

语法数据为 Scheme表达式的超集。故数据可用于把Scheme表达式表为数据对象。特别是,符号可用于表示标识符。

'(+ 23 42) ;的值为 (+ 23 42)
'(define (f x) (+ x 42)) ;的值为 (define (f x) (+ x 42))

这方便写操作Scheme代码的程序,特别是解释器和变换器。

继续

Scheme表达式求值时有一个继续想要其值。继续表示整个(默认)的未来计算。例如,非正式地 3的继续为表达式

(+ 1 3)

。正常情况下这些继续被隐藏,程序员不怎么管它们。在少数情况下,程序员需要显式与继续打交道。call-with-current-continuation过程让程序员创建重置当前继续的过程。call-with-current-continuation过程接受一个过程,立即以退出过程为参数调用它。退出过程被调用时参数就成为call-with-current-continuation调用的值。即退出过程放弃自己的继续,重置为call-with-current-continuation调用的继续。

下例中,表示加1的继续的退出过程绑定escape,然后用3调用它。escape调用的退出过程被放弃,于是3被传给加1的继续:

(+ 1 (call-with-current-continuation
       (lambda (escape)
         (+ 2 (escape 3))))) ;的值为 4

退出过程有无限的生存期:它可以在继续后调用且可多次调用。这使call-with-current-continuation明显比其它语言典型的非本地控制构造如异常强大。

Scheme 代码可组织为称为libraries的组件。每个库包含定义和表达式。它可从其它库导入定义和导出定义到其它库。

以下叫 (hello) 的库导出hello-world定义和导入base库和display库。hello-world是在输出一行Hello World的过程:

(define-library (hello)
  (export hello-world)
  (import (scheme base)
          (scheme display))
  (begin
    (define (hello-world)
      (display "Hello World")
      (newline))))

程序

库由其它库,最终 Scheme 程序调用。类似于库,程序包含导入、定义和表达式,还有特定的运行起点。从而一个程序通过它导入的库的传递闭包定义一个Scheme程序。

以下程序通过process-context库的command-line过程取得首个命令行参数。然后它用with-input-from-file打开文件并使之成为当前输入端口,最后自动关闭。然后,它调用read-line过程读入一行文本,再用write-string和newline输出该行,接着循环至文件结束:

(import (scheme base)
        (scheme file)
        (scheme process-context))
(with-input-from-file
  (cadr (command-line))
  (lambda ()
    (let loop ((line (read-line)))
      (unless (eof-object? line)
        (write-string line)
        (newline)
        (loop (read-line))))))

REPL

实现可以提供称为交互会话REPL(Read-Eval-Print Loop), 其中可一个一个地输入和处理导入、表达式和定义。REPL 开始时导入base库和其它可能的库。实现可提供REPL从文件读输入的操作模式,这文件一般不是程序,因为可以在非起始处导入。

以下是一个短的REPL会话. > 为输入提示符:

> ; A few simple things
> (+ 2 2)
4
> (sin 4)
Undefined variable: sin
> (import (scheme inexact))
> (sin 4)
-0.756802495307928
> (define sine sin)
> (sine 4)
-0.756802495307928
> ; Guy Steele's three-part test
> ; True is true ...
> #t
#t
> ; 100!/99! = 100 ...
> (define (fact n)
    (if (= n 0) 1 (* n (fact (- n 1)))))
> (/ (fact 100) (fact 99))
100
> ; If it returns the *right* complex number,
> ; so much the better ...
> (define (atanh x)
    (/ (- (log (+ 1 x))
          (log (- 1 x)))
       2))
> (atanh -2)
-0.549306144334055+1.5707963267949i

语法

空白与注释

空白(包括空格、制表符和换行符)可用于分隔标记,但自身对机器没有特殊含义。注释作用类似于空白,有以下几种注释:

  • ;开始到行末
  • #|开始到匹配的|#(可嵌套)
  • #;开始到下一个数据标记结束

Scheme有一种表示循环结构的记法,用#⟨n⟩=对象表示对象后,后面可用#⟨n⟩#引用该对象。

标识符可命名语法(称为语法关键字)或可保存值的位置(称为变量,该位置的值称为变量的值),在一个程序点可见的全部绑定称为环境。

数据类型

Scheme处理对象(也称为值),Scheme对象组织为称为类型的值集合。

数值

Scheme的数值类型对数学上的数字进行建模,Scheme支持的数值类型从一般到特殊有:

  • 复数
  • 实数
  • 有理数
  • 整数 Scheme区分精确数和不精确数,其中精确数写成精确的形式或者仅由精确数借助精确运算导出,不涉及中间非精确结果的精确数结果会与实现无关,算不了精确数宁可报错或返回非精确数。Scheme实现大多提供任意精度的精确整数和有理数。由于数值计算是个雷区,只能祝大家好运。

数值的记号由可选的前缀开始:

前缀 含义
#b 用二进制表示
#o 用八进制表示
#d 用十进制表示(默认)
#x 用十六进制表示
#e 这是精确数(在不带小数点也不带指数部分时默认)
#i 这是非精确数(在带小数点或指数部分时默认)

然后是以下复数形式之一:

  • ⟨real⟩
  • ⟨real⟩ @ ⟨real⟩
  • ⟨real⟩ + ⟨ureal⟩ i
  • ⟨real⟩ - ⟨ureal⟩ i
  • ⟨real⟩ + i
  • ⟨real⟩ - i
  • ⟨real⟩ ⟨infnan⟩ i
  • + ⟨ureal R⟩ i
  • - ⟨ureal R⟩ i
  • ⟨infnan⟩ i
  • + i
  • - i

其中⟨real⟩⟨ureal⟩+ ⟨ureal⟩- ⟨ureal⟩⟨infnan⟩⟨infnan⟩+inf.0-inf.0+nan.0-nan.0之一,⟨ureal R⟩形如:

  • ⟨uinteger⟩表示非负整数
  • ⟨uinteger⟩ / ⟨uinteger⟩表示分数
  • ⟨uinteger⟩ ⟨suffix⟩. ⟨uinteger⟩ ⟨suffix⟩⟨uinteger⟩ . ⟨suffix⟩⟨uinteger⟩ . <uinteger> ⟨suffix⟩表示实数,其中可选的指数部分⟨suffix⟩e+<uinteger>e+<uinteger>e+<uinteger>

布尔值

布尔值即直值,可以是真或假。在Scheme, 假对象记为#f#false,真对象记为#t#true。但在大多数要求真值的场合,非假的对象都被当作真。

序偶和列表

序偶为有两个分量的数据结构(可视为有两个域car和cdr的记录类型),分别以c1c2为分量的序偶记为(c1 . c2)

序偶常用于表示(单向链)列表,其中首分量(car'')表示列表首个元素,次分量(car’‘)表示余下的列表。Scheme 也有独特的空列表,用于列表中最后一个序偶的cdr,记为()。为方便见,以a1a2、……an为元素的列表可简记为(a1 a2 ... an),相当于(a1 . (a2 . (... (an . ()) ...)))。另外,(a1 a2 ... an t),相当于(a1 . (a2 . (... (an . t) ...)))

所有Scheme程序自身都可看作列表。

符号

符号是表示一个字符串的对象,该字符串称为其名字。与字符串不同,两个名字相同(区分大小写)的符号不可区分。符号有很多应用,例如可用来模仿其它语言的枚举。符号的记法有:

  • 由字母或!$%&*/:<=>?^_~之一开始,然后是由字母、数字或!$%&*/:<=>?^_~+-.@之一组成的序列
  • +-+.-..自身或后面紧接上一项的形式
  • +++-+@+..+.++.-+.@-+---@-..-.+-.--.@...+.-.@自身或后接由字母、数字或!$%&*/:<=>?^_~+-.@之一组成的序列
  • |包围的字符序列,其中可用转义序列\a\b\t\n\r\|\x⟨十六进制整数⟩;

字符

Scheme字符大致对应于文本字符。更准确地,它至少应包含ASCII字符,可以包括其它Unicode字符,甚至可能有与实现有关的扩展。字符可表示如#\⟨字符⟩#\x⟨十六进制整数值⟩,另外一些特殊字符另有专门记号:

字符表示 Unicode
#\alarm U+0007
#\backspace U+0008
#\delete U+007F
#\escape U+001B
#\newline U+000A
#\null U+0000
#\return U+000D
#\space U+0020
#\tab U+0009

为免歧义,上述字符表示后不能紧接标识符中可出现的字符。

字符串

字符串为字符的定长序列,可表示为由"包围的字符序列(容许换行),其中可用以下转义序列:

转义序列|表示字符 :—|:— \a|铃响,U+0007 \b|退格,U+0008 \t|制表符,U+0009 \n|换行,U+000A \r|回车,U+000D "|双引号,U+0022 \\|反斜杠,U+005C \||垂直线,U+007C \⟨行内空白⟩⟨行结束符⟩ ⟨行间空白⟩ |什么都不表示 \x⟨十六进制整数⟩;|有指定数值的字符

字符串中字符个数称为其长度,可以用从0开始的指标索引字符。

向量

向量和列表同为表示任意对象的有限序列的的线性数据结构。列表的元素通过遍历序偶链来存取,而向量的元素由整数指标存取。因此,向量比对应列表更适合随机访问且节省空间。

向量是定长的,其中元素个数称为其长度,可以用从0开始的指标索引元素。向量表示为#(obj . . . ),例如#(0 (2 2 2 2) "Anna")是一个长度为3的向量。

位向量

位向量是定长的字节(从0到255的精确整数)序列,表示二进制数据块,通常比对应向量省空间。其中字节个数称为其长度,可以用从0开始的指标索引字节。位向量表示为#u8(byte . . . ),如由0、255、13三个字节构成的位向量记为#u8(0 10 5)

过程

过程在Scheme为值。

记录

记录为结构化的值,是零个或更多个域\textit{域}的聚合,每一个存放单个位置。记录组织为\textit{记录类型}。一个记录类型可定义一个谓词、构造器、域访问器、域修改器。

端口

端口表示输入输出装置。对于Scheme,输入端口是一个可通过命令提供数据的Scheme对象,而输出端口是一个可接受数据的Scheme对象.

表达式

Scheme是表达式,每个表达式都有值(即使有时没有意义)。

基本的表达式类型

变量引用

variable

的值为变量⟨variable⟩的值(即变量所绑定位置的内容)。

字面表达式

(quote datum)

'datum

的值是外部表示为⟨datum⟩的Scheme对象。

另外,数值、字符串、字符、向量、位向量和布尔值等常量的值为自身,不必(但可以)用上述记号。

过程调用

(operator operand 1  . . . )

用于用各⟨operand⟩的值为参数调用⟨operator⟩的值(一个过程)并返回过程的返回值(通常一个,但可以多于一个)。各参数都会先于调用进行求值,但求值顺序与实现有关。

过程

(lambda formals body)

的值为一个新建的过程,它的主体⟨body⟩由零个或多个定义然后一个或多个表达式组成。至于⟨Formals⟩,它有以下形式之一:

  • (⟨variable 1 ⟩ . . . ) 表示过程接受固定个数的参数,在被调用时各实参会依次保存到各⟨variable ⟩绑定的位置。
  • ⟨variable⟩ 表示过程接受任意个数的参数,在被调用时各实参会依次组成一个新的列表,列表保存到⟨variable ⟩绑定的位置。
  • (⟨variable 1 ⟩ . . . ⟨variable n ⟩ . ⟨variable n+1 ⟩)表示过程接受n个或更多个参数,在被调用时前n个实参会依次保存到各⟨variable 1⟩⟨variable n⟩绑定的位置,其余实参会依次组成一个新的列表,列表保存到⟨variable ⟩绑定的位置。 每个通过lambda表达式创建的过程都与创建它时的环境关联,在过程被调用时,通过增加上述实参绑定扩充该环境,在这环境中依次求值⟨body⟩中各表达式(定义则与letrec*处理方法类似),返回最后一个表达式的值。另外,为了比较上的方便,每个过程视为对应一个内存位置。

分支

(if test consequent alternate)

(if test consequent)

用于在求值⟨test⟩得到#f时求值⟨alternate⟩并返回其值(没有⟨alternate⟩则返回值不明),否则求值⟨consequent⟩并返回其值。

赋值

(set! variable expression)

⟨expression⟩的值赋给变量⟨variable⟩

包含

(include string 1  string 2  . . . )

(include-ci string 1  string 2  . . . )

都用于顺序把由各文件名指定的文件内容读入并放到begin表达式中,用于取代上述的表达式。后一版本相当于在各文件前加上#!fold-case

派生的表达式类型

虽然原则上一节中描述的语言已经是图灵完备的(足以描述任何其它语言能描述的任务),但人们发现它们有一些常见用法,于是为此设立了一些语法糖。

条件

if表达式用于二路分支,但我们还常用多路分支,这当然总可化为嵌套的if表达式,不过由于太常用,人们又发明了cond表达式:

(cond clause 1  clause 2  . . . )

⟨clause⟩中的⟨test⟩会被依次求值直至首个不是#f的,其中各⟨clause⟩形如以下之一:

  • (⟨test⟩ ⟨expression 1 ⟩ . . . ),在⟨test⟩#f时,依次求值各⟨expression⟩,其中最后一个的值也是整个cond表达式的值(没有表达式的话则为⟨test⟩的值)。
  • (⟨test⟩ => ⟨expression⟩),在⟨test⟩#f时,求值⟨expression⟩得到一个接受一个参数的过程,然后以⟨test⟩的值调用它,其返回值作为整个cond表达式的值。 最后一个⟨clause⟩也可形如(else ⟨expression 1 ⟩ ⟨expression 2 ⟩ . . . ),如果所有⟨test⟩的值都是#f,依次求值各⟨expression⟩,其中最后一个的值也是整个cond表达式的值。如果没有else子名且所有⟨test⟩的值都是#f,则值与实现有关。

一种更特殊但仍然常见的多路分支中各分支条件均为相等性比较,于是有

(case ⟨key⟩ ⟨clause 1 ⟩ ⟨clause 2 ⟩ . . . )

表达式⟨key⟩会先被求值,然后依次寻找首个⟨clause⟩使其中某外部表示形式⟨datum⟩对应的Scheme对象与⟨key⟩的值相等(按eqv?),其中各⟨clause⟩形如以下之一:

  • ((⟨datum 1 ⟩ . . . ) ⟨expression 1 ⟩ ⟨expression 2 ⟩ . . . ),依次求值各⟨expression⟩,其中最后一个的值也是整个case表达式的值。
  • ((⟨datum 1 ⟩ . . . ) => ⟨expression⟩),求值⟨expression⟩得到一个接受一个参数的过程,然后以⟨key⟩的值调用它,其返回值作为整个case表达式的值。 最后一个⟨clause⟩也可形如(else => ⟨expression⟩),当没有匹配时求值⟨expression⟩得到一个接受一个参数的过程,然后以⟨key⟩的值调用它,其返回值作为整个case表达式的值。如果没有else子句也没有匹配则值与实现有关。

(and ⟨test 1 ⟩ . . . )会依次求值各⟨test⟩表达式直至碰到值为#f的,这时and表达式的值为#f,否则返回最后一个表达式的值(没有表达式则#t

(or ⟨test 1 ⟩ . . . )会依次求值各⟨test⟩表达式直至碰到值不为#f的,这时and表达式的值为这个值,否则返回#f

(when ⟨test⟩ ⟨expression 1 ⟩ ⟨expression 2 ⟩ . . . )⟨test⟩求值为非#f时,依次求值各⟨expression⟩,其中最后一个的值也是整个when表达式的值,否则值与实现有关。

(unless ⟨test⟩ ⟨expression 1 ⟩ ⟨expression 2 ⟩ . . . )⟨test⟩求值为#f时,依次求值各⟨expression⟩,其中最后一个的值也是整个when表达式的值,否则值与实现有关。

(cond-expand ⟨ce-clause 1 ⟩ ⟨ce-clause 2 ⟩ . . . )用于根据实现的特点做不同的事,其中各⟨ce-clause⟩形如(⟨feature requirement⟩ ⟨expression⟩ . . . )(最后一个也可形如(else ⟨expression⟩ . . . )),⟨feature requirement⟩为以下形式之一:

  • ⟨feature identifier⟩指定实现有指定特性标识符的情况
  • (library ⟨library name⟩)指定实现有指定库的情况
  • (and ⟨feature requirement⟩ . . . )
  • (or ⟨feature requirement⟩ . . . )
  • (not ⟨feature requirement⟩) 语义与cond类似。

绑定构造

绑定用于建立块结构,限制变量绑定的作用域。

绑定构造 绑定
(let ((⟨variable 1 ⟩ ⟨init 1 ⟩) . . . ) ⟨body⟩) 在当前环境中以某种顺序求值各⟨init⟩,然后在扩充的环境中把它们分别绑定到对应的变量⟨variable⟩
(let* ((⟨variable 1 ⟩ ⟨init 1 ⟩) . . . ) ⟨body⟩) 从当前环境开始顺序求值各⟨init⟩并扩充环境把它绑定到对应的变量⟨variable⟩,即后面的⟨init⟩可用前面的⟨init⟩的值。
(letrec ((⟨variable 1 ⟩ ⟨init 1 ⟩) . . . ) ⟨body⟩) 在当前环境通过把各⟨variable⟩绑定到新位置扩充环境,在新环境中以某种顺序求值各⟨init⟩把结果赋值给对应的变量⟨variable⟩
(letrec* ((⟨variable 1 ⟩ ⟨init 1 ⟩) . . . ) ⟨body⟩) 在当前环境通过把各⟨variable⟩绑定到新位置扩充环境,在新环境中顺序求值各⟨init⟩把结果赋值给对应的变量⟨variable⟩
(let-values ((⟨formals 1 ⟩ ⟨init 1 ⟩) . . . ) ⟨body⟩) 在当前环境中以某种顺序求值各⟨init⟩(可以有多个值),然后在扩充的环境中把它们分别绑定到⟨formals⟩
(let*-values ((⟨formals 1 ⟩ ⟨init 1 ⟩) . . . ) ⟨body⟩) 从当前环境开始顺序求值各⟨init⟩并扩充环境把它的各个值绑定到对应的变量⟨formals⟩,即后面的⟨init⟩可用前面的⟨init⟩的值。

其中⟨body⟩由零个或多个定义然后一个或多个表达式组成,会在新环境中求值,其中最后一个表达式的值也是绑定构造的值。

顺序

在一些只能放一个表达式的地方,有时我们还是想直接放多个表达式而懒得引入一个过程,这时用

(begin expression 1  expression 2  . . . )

⟨expression⟩会被顺序求值,最后一个的值会作为begin表达式的值。

另外,在各种⟨body⟩、REPL和程序的外层中还可用:

(begin expression or definition . . . )

其效果与直接写⟨expression or definition⟩ . . .同,但对宏作者有用。

迭代

由于Scheme有尾递归优化,其它语言中常用的循环在Scheme中用途不大,但Scheme还是提供了循环的语法糖:

(do ((variable 1  init 1  step 1 )
     ... )
    (test expression . . . )
    command . . . )

求值do表达式时,开始迭代前创建各变量⟨variable⟩,以某种顺序求值各表达式⟨init⟩并把其值赋给对应的变量⟨variable ⟩,然后在每次迭代中先求值⟨test⟩

  • 若得到#f则顺序求值各表达式⟨command⟩,再以某种顺序求值各表达式⟨step⟩并把其值赋给对应的变量⟨variable⟩,接着开始下一次迭代
  • 否则,各⟨expression⟩会被顺序求值,最后一个的值会作为do表达式的值(没有则表达式的值与实现有关)
(let variable bindings body)

是一个更一般的迭代结构,它与let表达式类似,只是把的把原来无名的内部过程绑定到一个变量⟨variable⟩以便作递归调用。

动态绑定

参数对象可以模拟动态作用域的变量,可用于指定配置选项(避免传来传去),实际上是一个接受零个参数的过程,我们把调用它时返回的值看作这参数的值。(make-parameter init)调用返回值为init的参数对象,(make-parameter init converter)调用则返回值为(converter init)的值的参数对象(convertor应为幂等的)。

(parameterize ((param 1  value 1 ) . . . )
              body)

则可以在执行⟨body⟩期间暂时把各参数对象⟨param⟩的值设为对应的表达式(converter ⟨value⟩)的值(其中converter是在创建参数对象时指定的),⟨body⟩中最后一个表达式的值作为parameterize表达式的值。

异常处理

(guard (variable
        cond clause 1  cond clause 2  . . . )
       body)

在被求值时会以一个异常处理器去执行⟨body⟩,在异常被抛出时,会把被抛出的对象绑定到变量⟨variable⟩,然后以guard表达式的继续和异常处理器如cond表达式中那样处理各⟨cond clause⟩,若没有子句可用则调用raise-continuable重新抛出异常。

模板

quote可以防止求值,但有时我们想构造一个部分而非全部成分已知的列表或向量。表达式(quasiquote ⟨qq template⟩)` ⟨qq template⟩的值就是根据模板⟨qq template⟩生成的数据结构,它与quote类似,但其中的(unquote ⟨qq template⟩),⟨qq template⟩会被替换为⟨qq template⟩的值,列表或向量中的(unquote-splicing ⟨qq template⟩),@⟨qq template⟩会被替换为⟨qq template⟩的值(必须是列表)的各个元素。quasiquote表达式可以嵌套,但只有层次与最外层相同时才进行替换,其中层次随quasiquote递增而随unquoteunquote-splicing递减。

过程分派

我们常期望一个过程在参数个数不同时做不同的事,例如实现默认参数的效果。

(case-lambda (formals 1 body 1) . . . )

的值为一个新的过程,这过程在被调用时首个与实参匹配的⟨formals⟩对应的⟨body⟩会被执行,其中各⟨formals⟩⟨body⟩语法与lambda表达式中同。

作为一种Lisp方言相同,Scheme也有强大的宏系统。也就是说,普通用户也能轻易定义和使用新的表达式类型。事实上,上一节中的派生的表达式类型就可以用这机制由基本的表达式类型定义。

使用用户定义的表达式类型与Scheme内置的表达式类型一致:

(keyword datum ...)

其中语法关键字keyword为惟一确定表达式类型的标识符,它与关键字与变量共用名字空间,可互相掩盖。在被求值时,会进行用户表达式类型指定的语法转换。如果转换结果中仍有表达式为用户定义的表达式类型,则会继续这过程。

要定义新的表达式类型,可用以下绑定构造:

(let-syntax ((keyword transformer spec) . . . ) body)

(letrec-syntax ((keyword transformer spec) . . . ) body)

它们分别对应于用于变量的letletrec,其中⟨transformer spec⟩指定新表达式类型对应的语法转换,它形如

(syntax-rules (literal . . . )
              (pattern template) . . . )

或者

(syntax-rules ellipsis 
              (literal . . . )
              (pattern template) . . . )

其中⟨ellipsis⟩为一个标识符,各⟨literal⟩为标识符。新表达式类型的使用会被转换为首个匹配的⟨pattern⟩对应的⟨template⟩⟨pattern⟩形如以下之一:

  • 一个标识符:各⟨literal⟩匹配与之有相同绑定(或都未绑定且名字相同)的标识符,其它标识符匹配任何东西,
  • 一个常量:匹配与之相等的对象(按equal?
  • (⟨pattern⟩ ...):匹配长度与⟨pattern⟩个数同且各元素依次匹配对应⟨pattern⟩的列表
  • (⟨pattern 1⟩ ⟨pattern 2⟩ ... . ⟨pattern n+1⟩):匹配有至少n个元素的列表或不正常列表,它的前n个元素依次匹配⟨pattern 1⟩⟨pattern n⟩,第n个尾匹配⟨pattern n+1⟩
  • (⟨pattern 1⟩ ... ⟨pattern k⟩ ⟨ellipsis⟩ ⟨pattern k+1⟩ ...):匹配至少有n-1个元素的列表,它前k-1个元素依次匹配⟨pattern 1⟩⟨pattern k-1⟩,后n-k个元素依次匹配⟨pattern k+1⟩⟨pattern n⟩,其余元素匹配⟨pattern k⟩,其中n为⟨pattern⟩个数
  • (⟨pattern 1⟩ ... ⟨pattern k⟩ ⟨ellipsis⟩ ⟨pattern k+1⟩ ... . ⟨pattern n+1⟩):匹配至少有n-1个元素的列表或不正常列表,它前k-1个元素依次匹配⟨pattern 1⟩⟨pattern k-1⟩,然后是若干元素匹配⟨pattern k⟩,然后n-k个元素依次匹配⟨pattern k+1⟩⟨pattern n⟩,余下的尾匹配⟨pattern n+1⟩
  • #(⟨pattern⟩ ...):匹配长度与⟨pattern⟩个数同且各元素依次匹配对应⟨pattern⟩的向量
  • #(⟨pattern 1⟩ ... ⟨pattern k⟩ ⟨ellipsis⟩ ⟨pattern⟩ ...):匹配至少有n-1个元素的向量,它前k-1个元素依次匹配⟨pattern 1⟩⟨pattern k-1⟩,后n-k个元素依次匹配⟨pattern k+1⟩⟨pattern n⟩,其余元素匹配⟨pattern k⟩,其中n为⟨pattern⟩个数 而⟨template⟩则形如以下之一:
  • 一个标识符:若为出现于⟨pattern⟩的非⟨literal⟩_⟨ellipsis⟩的标识符,则生成宏使用中的对应部分;对于⟨literal⟩标识符,作为自由变量则生成对宏定义点处对应绑定的引用,作为绑定变量则改名;其它标识符会按字面生成它。
  • 一个常量:生成该常量
  • (⟨element⟩ ...):生成对应列表
  • (⟨element⟩ ⟨element⟩ ... . ⟨template⟩):列表对应的列表或不正常列表
  • (⟨ellipsis⟩ ⟨template⟩):与⟨template⟩类似,但其中⟨ellipsis⟩失去特殊含义。例如(⟨ellipsis⟩ ⟨ellipsis⟩)生成单个⟨ellipsis⟩
  • #(⟨element⟩ ...):生成对应向量 其中⟨element⟩⟨template⟩,后面可选跟⟨ellipsis⟩syntax-rules中不指定则默认...),在后跟⟨ellipsis⟩时子模板与与子模式匹配的宏使用部分对应。

在编写宏时可用(syntax-error ⟨message⟩ ⟨args⟩ . . . )指出已经出现语法错误,一旦转换产生它就会抛出错误,不用继续语法转换过程。

程序结构

程序

Scheme程序由一个或以上的导入声明开始,然后是一系列定义和表达式,它们会被顺序执行。

导入声明

导入的声明的样子如:

(import import-set . . . )

其中每个⟨import set⟩指定导入什么,有以下形式之一:

  • ⟨library name⟩用于指定库name中导出的所有绑定
  • (only ⟨import set⟩ ⟨identifier⟩ . . . )用于指定⟨import set⟩中有指定标识符的绑定
  • (except ⟨import set⟩ ⟨identifier⟩ . . . )用于指定⟨import set⟩中除有指定标识符的绑定外的其它绑定
  • (prefix ⟨import set⟩ ⟨identifier⟩)用于指定⟨import set⟩中所有绑定,但在各标识符前面分别加上前缀
  • (rename ⟨import set⟩ (⟨identifier 1 ⟩ ⟨identifier 2 ⟩) . . . )用于指定⟨import set⟩中所有绑定,但把指定的标识符⟨identifier 1 ⟩改名为相应的⟨identifier 2 ⟩

在程序中不能导入两个同名的绑定,也不能重定义或set!它们。在REPL中倒可以。

定义

变量定义

变量定义给一个标识符绑定初始值,其语法如:

  • (define ⟨variable⟩ ⟨expression⟩)给标识符⟨variable⟩绑定初值⟨expression⟩
  • (define (⟨variable⟩ ⟨formals⟩) ⟨body⟩)相当于(define ⟨variable⟩ (lambda (⟨formals⟩) ⟨body⟩))
  • (define (⟨variable⟩ . ⟨formal⟩) ⟨body⟩)相当于(lambda ⟨formal⟩ ⟨body⟩))
  • (define-values ⟨formals⟩ ⟨expression⟩)用于把⟨formals⟩中各标识符分别绑定到表达式⟨Expression⟩的各个值

在程序顶层,如果⟨variable⟩已经绑定到非语法值(在其它情况,定义先把它绑定到一个新位置),定义(define ⟨variable⟩ ⟨expression⟩)实际上相当于赋值(set! ⟨variable⟩ ⟨expression⟩)

定义也可出现于lambda、let、let、letrec、letrec、let-values、let*-values、let-syntax、letrec-syntax、parameterize、guard和case-lambda表达式中的⟨body⟩的开首,这些绑定在且仅在整个⟨body⟩中有效,类似letrec*

语法定义

语法定义形如:

(define-syntax keyword transformer spec)

其中⟨Keyword⟩为一个标识符而 ⟨transformer spec⟩为syntax-rules的实例。然而,定义不能定义一个会影响定义自身含义的标识符。如(define define 3)(begin (define begin list))是非法的。

记录类型定义

记录类型定义用于引入新的记录类型。记录类型的值称为记录,每个记录由若干个域组成。记录类型定义形如:

(define-record-type name constructor pred field . . . )
  • ⟨name⟩ 为一个标识符,用于指定新类型的名称
  • ⟨pred⟩ 为一个标识符,将给它绑定一个过程,这过程接受一个参数,返回参数是否属于此记录类型
  • ⟨constructor⟩形如(⟨constructor name⟩ ⟨field name⟩ . . . )是标识符的列表,用于指定构造器和各域的名字,⟨constructor name⟩被绑定为一个过程,它依次接受各⟨field name⟩为参数,返回各域为相应实参的记录
  • ⟨field⟩形如(⟨field name⟩ ⟨accessor name⟩)(⟨field name⟩ ⟨accessor name⟩ ⟨modifier name⟩)
    • ⟨accessor name⟩会绑定为一个过程,它接受此记录类型作惟一参数,返回对应域⟨field name⟩的值
    • ⟨modifier name⟩会绑定为一个过程,它接受此记录类型的记录和另一参数,把记录对应域⟨field name⟩设为后一参数

库是可重用的组件,用于组织程序。Scheme中库的定义形如:

(define-library library name library declaration . . . )

其中⟨library name⟩为一个由标识符组成的列表,用于惟一标识库(首个元素为scheme的留给标准库,首个元素为srfi的留给Scheme实现请求)。而⟨library declaration⟩可以有以下形式:

  • (export ⟨export spec⟩ . . . )用于指定导出(即其它库和程序可见的)的绑定,其中⟨identifier⟩指定按原名导出的一个标识符,(rename ⟨identifier 1 ⟩ ⟨identifier 2 ⟩)指定以名字⟨identifier 2 ⟩导出原名⟨identifier 1 ⟩的标识符
  • (import ⟨import set⟩ . . . )用于指定从其它库导入的绑定,语法同程序的导入声明
  • (begin ⟨command or definition⟩ . . . )用于指定这个库的主体特别是定义绑定
  • (include ⟨filename 1 ⟩ ⟨filename 2 ⟩ . . . )用于把一些文件的内容包含进来作为上述begin声明的参数
  • (include-ci ⟨filename 1 ⟩ ⟨filename 2 ⟩ . . . )用于把一些文件的内容包含进来作为上述begin声明的参数(大小写折叠)
  • (include-library-declarations ⟨filename 1 ⟩ ⟨filename 2 ⟩ . . . )用于把一些文件的内容包含进来作为一些这个库的⟨library declaration⟩
  • (cond-expand ⟨ce-clause 1 ⟩ ⟨ce-clause 2 ⟩ . . . )用于指定一些实现相关的⟨library declaration⟩,语法同cond-expand表达式

当加载库时其中表达式被顺序执行,如果引用了一个库中的定义,该库会先被加载。

标准库

以下列出R7RS中指定的标准过程。如没写明,都在库base中。它们一般服从以下命名约定:

  • 名字以?结尾的过程接受一个参数,返回一个布尔值
  • 名字由两个由->分隔的类型名组成的过程过程接受一个前一类型的参数,返回后一类型的相应表示
  • 名字以!结尾的过程返回值不重要,要的是副作用

数据结构

等价性

从细到粗的eq?、eqv?、equal?三个谓词都用于判断它的两个参数是否等价(判断是可终止的),参数类型不同时返回#f,在两个参数类型相同时比较规则如下:

类型 eq? eqv? equal?
布尔 同为#t或同为#f 同为#t或同为#f 同为#t或同为#f
符号 按symbol=?相等 按symbol=?相等 按symbol=?相等
精确数 与实现有关 按=相等 按=相等
非精确数 与实现有关 按=相等且通过有限次Scheme标准算术过程得到的结果(如非nan)仍按eqv?相等 按=相等且通过有限次Scheme标准算术过程得到的结果(如非nan)仍按eqv?相等
字符 与实现有关 按char=?相等 按char=?相等
空列表 相等 相等 相等
序偶 内存位置相同 内存位置相同 递归比较
向量 内存位置相同(空向量结果与实现有关) 内存位置相同 递归比较
位向量 内存位置相同(空向量结果与实现有关) 内存位置相同 递归比较
字符串 内存位置相同(空串结果与实现有关) 内存位置相同 递归比较
记录 内存位置相同 内存位置相同 与实现有关
过程 内存位置相同 内存位置相同 与实现有关

数值

在以下过程中,如无特别说明,在所有参数均为精确数时返回精确数,在有某个参数为非精确数时返回非精确数(除非实现能保证精确性),在有参数为nan时返回nan。

表达式 用途
(number? obj) 返回obj是否数值
(complex? obj) 返回obj是否复数
(real? obj) 返回obj是否实数
(rational? obj) 返回obj是否有理数
(integer? obj) 返回obj是否整数
(exact? z) 返回复数z是否精确的
(inexact? z) 返回复数z是否不精确
(exact-integer? z) 返回复数z是否精确整数
(= z1 z2 z3 . . . ) 返回各复数参数是否数值相等,不区分正负零,遇nan即返回#f
(< x1 x2 x3 . . . ) 返回各实数参数是否按数值递增,不区分正负零,遇nan即返回#f
(> x1 x2 x3 . . . ) 返回各实数参数是否按数值递减,不区分正负零,遇nan即返回#f
(zero? z) 返回复数z是否0
(positive? x) 返回实数x是否正
(negative? x) 返回实数x是否负
(odd? n) 返回整数n是否奇数
(even? n) 返回整数n是否偶数
(max x1 x2 . . . ) 返回实数参数中最大者
(min x1 x2 . . . ) 返回实数参数中最小者
(+ z1 . . . ) 返回所有复数参数之和
(* z 1 . . . ) 返回所有复数参数之积
(- z) 返回复数z的相反数
(- z1 z2 . . . ) 返回复数z1减去所有其余复数参数的差
(/ z) 返回复数z的倒数
(/ z1 z2 . . . ) 返回复数z1除掉所有其余复数参数的商
(abs x) 返回实数x的绝对值
(floor/ n1 n2 ) 返回整数q和r使$n_1 = n_2 q + r$,其中$q=\lfloor \frac{n_1}{n_2} \rfloor$
(floor-quotient n1 n2 ) 返回$\lfloor \frac{n_1}{n_2} \rfloor$
(floor-remainder n1 n2 ) 返回$民n_1-n_2\lfloor \frac{n_1}{n_2} \rfloor$
(truncate/ n 1 n 2 ) 返回整数q和r使$n_1 = n_2 q + r$,其中$q=\mathrm{trancate}( \frac{n_1}{n_2} )$
(truncate-quotient n 1 n 2 ) 返回$\mathrm{trancate}( \frac{n_1}{n_2} )$
(truncate-remainder n 1 n 2 ) 返回$n_1-n_2\mathrm{trancate}( \frac{n_1}{n_2} )$
(quotient n1 n2 ) 相当于truncate-quotient
(remainder n1 n2 ) 相当于truncate-remainder
(modulo n1 n2 ) 相当于floor-remainder
(gcd n1 . . . ) 返回各整数参数的最大公约数(非负)
(lcm n1 . . . ) 返回各整数参数的最小公倍数(非负)
(numerator q) 返回有理数q的分子
(denominator q) 返回有理数q的分母(正)
(floor x) 返回不大于实数x的最大整数
(ceiling x) 返回不小于实数x的最小整数
(truncate x) 返回绝对值不大于实数x的绝对值最大的整数
(round x) 返回最接近实数x的整数
(rationalize x y) 返回与实数x之差不大于y的极简(没有其它有理数分子与分母都不大于它的分子与分母且仍有前述性质)有理数
(square z) 相当于(* z z)
(exact-integer-sqrt k) 返回非负精确整数s和r使$k=s^2+r$且$k<(s+1)^2$
(expt z1 z2) 返回$z1^{z2}$
(inexact z) 返回与复数z最接近的不精确数
(exact z) 返回复数z最接近的精确数
(number->string z) 返回复数z的十进制字符串表示
(number->string z radix) 返回复数z的radix(2、8、10或16)进制字符串表示
(string->number string) 返回字符串string表示的十进制数值
(string->number string radix) 返回字符串string表示的radix(2、8、10或16)进制数值

另外,inexact库提供:

表达式 用途
(finite? z) 返回复数z是否实部和虚部都有限(不是+inf.0、-inf.0或+nan.0)
(infinite? z) 返回复数z是否实部或虚部为无穷(+inf.0或-inf.0)
(nan? z) 返回复数z是否实部或虚部为+nan.0
(exp z) 返回$e^z$
(log z) 返回$\ln(z)$
(log z1 z2 ) 返回$\log_{z2}(z1)$
(sin z) 返回$\sin(z)$
(cos z) 返回$\cos(z)$
(tan z) 返回$\tan(z)$
(asin z) 返回$\asin(z)$
(acos z) 返回$\acos(z)$
(atan z) 返回$\atan(z)$
(atan y x) 相当于(angle (make-rectangular x y))
(sqrt z) 返回复数z的主平方根,有正实部或为有非负虚部的纯虚数

complex库提供:

表达式 用途
(make-rectangular x1 x2) 返回分别以x1和x2为实部和虚部的复数
(make-polar x3 x4 ) 返回分别以x3和x4为模和辐角的复数
(real-part z) 返回复数z的实部
`(imag-part z 返回复数z的虚部
(magnitude z) 返回复数z的模
(angle z) 返回复数z的辐角($-\pi$到$\pi$间)

布尔值

表达式 用途
(not obj) obj为#f时返回#t,否则#f
(boolean? obj) 返回obj是否布尔值
(boolean=? boolean1 boolean2 boolean3 . . . ) 返回各参数是否合为布尔值且相同

序偶与列表

表达式 用途
(pair? obj) 返回obj是否序偶
(cons obj1 obj2) 返回由obj1和obj2组成的序偶
(car pair) 返回序偶pair的car域内容
(cdr pair) 返回序偶pair的cdr域内容
(set-car! pair obj) 设置序偶pair的car域为obj
(set-cdr! pair obj) 设置序偶pair的cdr域为obj
(caar pair) 相当于(car (car pair))
(cadr pair) 相当于(car (cdr pair))
(cdar pair) 相当于(cdr (car pair))
(cddr pair) 相当于(cdr (cdr pair))
(null? obj) 返回obj是否空列表
(list? obj) 返回obj是否正常列表(有限长度且由空列表终结)
(make-list k) 返回有k个元素的新列表
(make-list k fill) 返回有k个元素的新列表,每个为fill
(list obj . . . ) 返回由各obj组成的新列表
(length list) 返回列表list的长度
(append list . . . ) 返回把各列表list(最后一个可以不正常)串接进来得到的列表,多于一个参数时为新建的
(reverse list) 返回反转列表list中元素顺序得到的新列表
(list-tail list k) 返回对list取k次cdr得到的结果
(list-ref list k) 返回列表list的第k个元素
(list-set! list k obj) 把列表list的第k个元素设为obj
(memq obj list) 返回列表中car为obj(eq?比较)的首个子列表,没有有则#f
(memv obj list) 返回列表中car为obj(eqv?比较)的首个子列表,没有有则#f
(member obj list) 返回列表中car为obj(equal?比较)的首个子列表,没有有则#f
(member obj list compare) 返回列表中car为obj(compare比较)的首个子列表,没有有则#f
(assq obj alist) 返回列表alist中car为obj的首个元素(用eq?比较),没有则#f
(assv obj alist) 返回列表alist中car为obj的首个元素(用eqv?比较),没有则#f
(assoc obj alist) 返回列表alist中car为obj的首个元素(用equal?比较),没有则#f
(assoc obj alist compare) 返回列表alist中car为obj的首个元素(用compare比较),没有则#f
(list-copy obj) 返回元素同list的新列表

另外,cxr库提供从caaar到cddddr共24个过程用于从嵌套的序偶中提取信息。

符号

表达式|用途 :—|:— (symbol? obj)|返回obj是否符号 (symbol=? symbol1 symbol2 symbol3 . . . )|返回各参数是否全部名字相等 (symbol->string symbol)| `(string->symbol string)

字符

表达式 用途
(char? obj) 返回obj是否字符
(char=? char1 char2 char3 . . . ) 返回各参数字符串是否相等
`(char<? char1 char2 char3 . . .) 返回各参数字符串是否按递增顺序排列
(char>? char1 char2 char3 . . . ) 返回各参数字符串是否按递降顺序排列
(char<=? char1 char2 char3 . . . ) 返回各参数字符串是否按不降顺序排列
(char>=? char1 char2 char3 . . . ) 返回各参数字符串是否按不增顺序排列
(char->integer char) 返回字符char的Unicode值
(integer->char n) 返回对应于unicode值精确整数n的字符

另外,char库提供:

表达式 用途
(char-ci=? char1 char2 char3 . . . ) 返回各参数字符串作大小写折叠后是否相等
(char-ci<? char1 char2 char3 . . . ) 返回各参数字符串作大小写折叠后是否按递增顺序排列
(char-ci>? char1 char2 char3 . . . ) 返回各参数字符串作大小写折叠后是否按递降顺序排列
(char-ci<=? char1 char2 char3 . . . ) 返回各参数字符串作大小写折叠后是否按不降顺序排列
(char-ci>=? char1 char2 char3 . . . ) 返回各参数字符串作大小写折叠后是否按不增顺序排列
(char-alphabetic? char) 返回字符char是否字母
(char-numeric? char) 返回字符char是否数字
(char-whitespace? char) 返回字符char是否空白
(char-upper-case? letter) 返回字符letter是否大写
(char-lower-case? letter) 返回字符letter是否小写
(digit-value char) 返回字符char表示的十进制数值或者#f(如不表示)
(char-upcase char) 返回字符char的大写
(char-downcase char) 返回字符char的小写
(char-foldcase char) 返回字符char的大小写折叠

字符串

表达式 用途
(string? obj) 返回obj是否字符串
(make-string k) 返回长度为k的新字符串
(make-string k char) 返回长度为k的新字符串,其中每个字符初始化为char
(string char . . . ) 返回由各字符char组成的新字符串
(string-length string) 返回字符串string的长度
(string-ref string k) 返回字符串string中指标为k的字符
(string-set! string k char) 把字符串string中指标为k的字符设为char
(string=? string1 string2 string3 . . . ) 返回各参数字符串是否都相等
(string<? string1 string2 string3 . . . ) 返回各参数字符串是否按递增顺序排列
(string>? string1 string2 string3 . . . ) 返回各参数字符串是否按递降顺序排列
(string<=? string1 string2 string3 . . . ) 返回各参数字符串是否按不降顺序排列
(string>=? string1 string2 string3 . . . ) 返回各参数字符串是否按不增顺序排列
(substring string start end) 同string-copy
(string-append string . . . ) 返回把各string串接起来得到的新字符串
(string->list string) 返回对应于字符串string的字符列表
(string->list string start) 返回对应于字符串string的字符列表
(string->list string start end) 返回对应于字符串string的字符列表
(list->string list) 返回字符列表list对应的新字符串
(string-copy string) 返回内容同string的新字符串
(string-copy string start) 返回内容同string的新字符串
(string-copy string start end) 返回内容同string的新字符串
(string-copy! to at from) 把字符串from中字符复制到字符串to中指标从at开始的地方
(string-copy! to at from start) 把字符串from中字符复制到字符串to中指标从at开始的地方
(string-copy! to at from start end) 把字符串from中字符复制到字符串to中指标从at开始的地方
(string-fill! string fill) 把字符串string中所有字符设为fill
(string-fill! string fill start) 把字符串string中指标从start开始的字符设为fill
(string-fill! string fill start end) 把字符串string中指标从start到end的字符设为fill

另外,char库提供:

表达式 用途
(string-ci=? string1 string2 string3 . . . ) 返回各参数字符串作大小写折叠后是否相等
(string-ci<? string1 string2 string3 . . . ) 返回各参数字符串作大小写折叠后是否按递增顺序排列
(string-ci>? string1 string2 string3 . . . ) 返回各参数字符串作大小写折叠后是否按递降顺序排列
(string-ci<=? string1 string2 string3 . . . ) 返回各参数字符串作大小写折叠后是否按不降顺序排列
(string-ci>=? string1 string2 string3 . . . ) 返回各参数字符串作大小写折叠后是否按不增顺序排列
(string-upcase string) 返回字符串string的大写形式
(string-downcase string) 返回字符串string的小写形式
(string-foldcase string) 返回字符串string的大小写折叠形式

向量

表达式 用途
(vector? obj) 返回obj是否向量
(make-vector k) 返回长度为k的新向量
(make-vector k fill) 返回长度为k的新向量,其中每个字节初始化为byte
(vector obj . . . ) 返回以各obj为元素的新向量
(vector-length vector) 返回vector的长度
(vector-ref vector k) 返回vector指标为k的元素
(vector-set! vector k obj) 把vector指标为k的元素设为obj
(vector->list vector) 返回对应于向量vector的新列表
(vector->list vector start) 返回对应于向量vector的新列表
(vector->list vector start end) 返回对应于向量vector的新列表
(list->vector list) 返回对应于列表vector的新向量
(vector->string vector) 返回字符向量vector对应的新字符串
(vector->string vector start) 返回字符向量vector对应的新字符串
(vector->string vector start end) 返回字符向量vector对应的新字符串
(string->vector string) 返回字符串string对应的新字符向量
(string->vector string start) 返回字符串string对应的新字符向量
(string->vector string start end) 返回字符串string对应的新字符向量
(vector-copy vector) 返回元素同vector的新向量
(vector-copy vector start) 返回元素同vector的新向量
(vector-copy vector start end) 返回元素同vector的新向量
(vector-copy! to at from) 把向量from中字节复制到向量to中指标从at开始的地方
(vector-copy! to at from start) 把向量from中字节复制到向量to中指标从at开始的地方
(vector-copy! to at from start end) 把向量from中字节复制到向量to中指标从at开始的地方
(vector-append vector . . . ) 返回把各vector串接起来得到的新向量
(vector-fill! vector fill) 把vector中所有元素设为fill
(vector-fill! vector fill start) 把vector中所有指标从start开始的元素设为fill
(vector-fill! vector fill start end) 把vector所有指标从start到end(不含)的元素设为fill

位向量

表达式 用途
(bytevector? obj) 返回obj是否位向量
(make-bytevector k) 返回长度为k的新位向量
(make-bytevector k byte) 返回长度为k的新位向量,其中每个字节初始化为byte
(bytevector byte . . . ) 返回以各byte为元素的新位向量
(bytevector-length bytevector) 返回bytevector的长度
(bytevector-u8-ref bytevector k) 返回bytevector中指标为k的元素
(bytevector-u8-set! bytevector k byte) 把bytevector指标为k的元素设置为byte
(bytevector-copy bytevector) 返回内容与bytevector同的新位向量
(bytevector-copy bytevector start) 返回内容为bytevector中从指标start开始字节的新位向量
(bytevector-copy bytevector start end) 返回内容为bytevector中从指标start开始到end(不含)字节的新位向量
(bytevector-copy! to at from) 把位向量from中字节复制到位向量to中指标从at开始的地方
(bytevector-copy! to at from start) 把位向量from中字节复制到位向量to中指标从at开始的地方
(bytevector-copy! to at from start end) 把位向量from中字节复制到位向量to中指标从at开始的地方
(bytevector-append bytevector . . . ) 返回由串接各bytevector所得的新位向量
(utf8->string bytevector) 返回UTF-8解码所得字符串
(utf8->string bytevector start) 返回UTF-8解码所得字符串
(utf8->string bytevector start end) 返回UTF-8解码所得字符串
(string->utf8 string) 返回字符串的UTF-8编码
(string->utf8 string start) 返回字符串的UTF-8编码
(string->utf8 string start end) 返回字符串的UTF-8编码

控制结构

在每个Scheme表达式被求值时,都有一个继续,表示预期在求值这表达式后应做的事。

表达式 用途
(procedure? obj) 返回obj是否过程
(apply proc arg 1 . . . args) 以表达式list (append (list arg 1 . . . ) args)的值的各个元素为实参调用过程proc
(map proc list1 list2 . . . ) 返回一个长度为最短参数的长度的列表,其中各元素分别由以各list中对应元素为实参调用过程proc的返回值
(string-map proc string1 string2 . . . ) 返回一个长度为最短参数的长度的字符串,其中各字符分别由以各string中对应字符为实参调用过程proc的返回值
(vector-map proc vector1 vector2 . . . ) 返回一个长度为最短参数的长度的向量,其中各元素分别由以各vector中对应元素为实参调用过程proc的返回值
(for-each proc list1 list2 . . . ) 依次以各list中对应元素为实参调用过程proc直到用完其中某列表的元素
(string-for-each proc string1 string2 . . . ) 依次以各string中对应字符为实参调用过程proc直到用完其中某字符串的字符
(vector-for-each proc vector1 vector2 . . . ) 依次以各vector中对应元素为实参调用过程proc直到用完其中某向量的元素
(call-with-current-continuation proc) 以当前的继续逃逸过程为参数调用单参过程proc并以其返回值为整个表达式的值,调用逃逸过程会导致以调用逃逸过程的实参为实参去到call-with-current-continuation的继续。
(call/cc proc) call-with-current-continuation
(values obj . . .) 其各个值分别为各obj
(call-with-values producer consumer) 先调用无参过程producer,然后以其各返回值为实参调用consumer,后者的返回值作为整个表达式的值
(dynamic-wind before thunk after) 调用无参过程thunk并返回其值,但保证每次进入thunk的活动范围前调用无参过程before,每次退出thunk的活动范围前调用无参过程after,即使可能涉及逃逸过程

异常

Scheme系统在动态环境中隐匿了一个当前异常处理器,异常处理器是接受一个参数的过程,在抛出一个对象引发异常时,会以该对象为参数调用当前异常处理器。

表达式 用途
(with-exception-handler handler thunk) handler为当前异常处理器调用无参过程thunk并返回其值
(raise obj) 抛出obj引发异常,即以它为参数调用当前异常处理器,它返回后会级联地调用handler的当前异常处理器
(raise-continuable obj) 抛出obj引发异常,即以它为参数调用当前异常处理器,其返回值作为raise-continuable的值
(error message obj . . .) 抛出一个新的错误对象(其中字符串message为错误信息、各obj为证据)引发异常
(error-object? obj) 返回obj是否错误对象
(error-object-message error-object) 返回错误对象error-object的信息
(error-object-irritants error-object) 返回错误对象error-object的证据列表
(read-error? obj) 返回obj是否由读过程抛出
(file-error? obj) 返回obj是否由打开文件端口抛出

I/O

端口

Scheme中的端口对象用于表示输入或输出设备:输入端口可提供数据,输出端口可提供数据;文本端口以字符单元操作,二进制端口以字节为单元操作。

表达式 用途
(input-port? obj) 返回obj是否输入端口
(output-port? obj) 返回obj是否输出端口
(textual-port? obj) 返回obj是否文本端口
(binary-port? obj) 返回obj是否二进制端口
(port? obj) 返回obj是否端口
(input-port-open? port) 返回端口obj是否处于打开状态且可进行输入操作
(output-port-open? port) 返回端口obj是否处于打开状态且可进行输出操作
(current-input-port) 返回当前标准输入端口
(current-output-port) 返回当前标准输出端口
(current-error-port) 返回当前标准错误输出端口
(close-port port) 关闭端口
(close-input-port port) 关闭输入端口
(close-output-port port) 关闭输出端口
(open-input-string string) 返回一个文本输入端口,它提供字符串string中的字符
(open-output-string) 返回一个文本输出端口
(get-output-string port) 返回写到(open-output-string)创建的端口的字符串
(open-input-bytevector bytevector) 返回一个二进制输入端口,它提供字符串bytevector中的字节
(open-output-bytevector) 返回一个二进制输出端口
(get-output-bytevector port) 返回写到(open-output-bytevector)创建的端口的位向量
(call-with-port port proc) port为参数调用proc,然后关闭port,以proc的值为call-with-port表达式的值

file库提供:

表达式 用途
(call-with-input-file string proc) 以文件名string对应的文本输入端口为参数调用proc,然后关闭端口,以proc的值为这表达式的值
(call-with-output-file string proc) 以文件名string对应的文本输出端口为参数调用proc,然后关闭端口,以proc的值为这表达式的值
(with-input-from-file string thunk) 以文件名string对应的文本输入端口为标准输入调用无参过程proc,然后关闭端口并恢复原先的标准输入,以proc的值为这表达式的值
(with-output-to-file string thunk) 以文件名string对应的文本输出端口为标准输出调用无参过程proc,然后关闭端口并恢复原先的标准输出,以proc的值为这表达式的值
(open-input-file string) 打开并返回string对应的文本输入端口
(open-binary-input-file string) 打开并返回string对应的二进制输入端口
(open-output-file string) 打开并返回string对应的文本文本输出端口
(open-binary-output-file string) 打开并返回string对应的二进制输出端口

本节中省略port都视为(current-input-port)

表达式 用途
(read-char)  
(read-char port) 返回从文本输入端口port读入的下一字符,没有则返回end-of-file对象
(peek-char)  
(peek-char port) 返回从文本输入端口port读入的下一字符但不更新指针,没有则返回end-of-file对象
(read-line)  
(read-line port) 返回从文本输入端口port读入的下一行字符串(不含换行),没有则返回end-of-file对象
(eof-object? obj) 返回obj是否eof-object对象
(eof-object) 返回一个eof-object对象
(char-ready?)  
(char-ready? port) 返回从文本输入端口port读入下一字符是否一定不阻塞
(read-string k)  
(read-string k port) 返回从文本输入端口port读入的下面最多k个字符组成的字符串,没有则返回end-of-file对象
(read-u8)  
(read-u8 port) 返回从二进制输入端口port读入的下一字节,没有则返回end-of-file对象
(peek-u8)  
(peek-u8 port) 返回从二进制输入端口port读入的下一字节但不更新指针,没有则返回end-of-file对象
(u8-ready?)  
(u8-ready? port) 返回从二进制输入端口port读入的下一字节是否一定不阻塞,没有则返回end-of-file对象
(read-bytevector k)  
(read-bytevector k port) 返回从二进制输入端口port读入的下面最多k个字节组成的位向量,没有则返回end-of-file对象
(read-bytevector! bytevector)  
(read-bytevector! bytevector port)  
(read-bytevector! bytevector port start)  
(read-bytevector! bytevector port start end) 从二进制输入端口port把最多end-start个字节读到位向量bytevector从指标start开始的位置,返回实际读入的字节数,没有则返回end-of-file对象

read库则提供:

表达式 用途
(read)  
(read port) 从文本输入端口port读入一个Scheme对象的外部表示形式并返回对应对象,没有则返回end-of-file对象

本节中省略port都视为(current-output-port)

表达式|用途 :—|:— (newline)| (newline port)|把换行符写到文本输出端口port (write-char char)| (write-char char port)|把字符char写到文本输出端口port (write-string string)| (write-string string port)| (write-string string port start)| (write-string string port start end)|把字符串string指标从start开始到end(不含)的子串写到文本输出端口port (write-u8 byte)| (write-u8 byte port)|把字节byte写到二进制输出端口port (write-bytevector bytevector) (write-bytevector bytevector port) (write-bytevector bytevector port start) (write-bytevector bytevector port start end)|把位向量bytevector中指标从startend的字节写到二进制输出端口port (flush-output-port)| (flush-output-port port)|清洗输出端口port`的缓冲区

write库提供:

表达式 用途
(write obj)  
(write obj port) obj的外部表示写到文本输出端口port,其中字符串用双引号形式(双引号和反斜杠用反斜杠转义)、含非ASCII字符的符号用竖线转义、字符用#\记号,必要时为防无穷循环才用标号。
(write-shared obj)  
(write-shared obj port) write类似,但共享结构都会用标号
(write-simple obj)  
(write-simple obj port) write类似,但不用标号,可能不能终止
(display obj)  
(display obj port) obj的外部人类友好表示写到文本输出端口port,其中对于字符串相当于write-string、对于字符相当于write-char、符号不转义,必须可终止

其它

元编程

eval库提供:

表达式 用途
(environment list1 . . . ) 创建由导入各list(语法和语义同import set)(不可变)
(eval expr-or-def environment-specifier) 在环境environment-specifier中求值expr-or-def并返回之

repl库提供:

表达式 用途
(interaction-environment) 返回一个通常包含(scheme base)库中导出绑定的环境(可变)

r5rs库提供:

表达式 用途
(scheme-report-environment 5) 返回只包含R5RS库绑定的环境(不可变)
(null-environment 5) 返回仅有R5RS库定义的语法关键字的环境(不可变)

时间

time库提供:

表达式 用途
(current-second) 返回表示当前时间的非准确数,从1970年1月1日算起的秒数
(current-jiffy) 返回表示当前时间的准确整数,单位和起点(多次运行程序可能不同)都与实现有关
(jiffies-per-second) 返回表示一秒中时间单位个数的准确整数

懒惰求值

lazy库提供一些工具用于实现表达式按需求值。为此先用delay把表达式包装进一个promise对象中,然后在需要时force它以强迫求值该表达式。表达式的值在promise对象首次被强迫求值时就记下来,以后再强迫求值同一promise对象不再重复求值而直接返回原先结果。

表达式 用途
(delay ⟨expression⟩) 展开为一个promise对象,它在被强迫求值时求值⟨expression⟩
(delay-force ⟨expression⟩) 类似于展开为(delay (force expression)),但被强迫求值时处于尾调用
(force promise) 返回对promise强迫求值的结果
(promise? obj) 返回obj是否promise对象
(make-promise obj) 返回一个promise对象,它在被强迫求值时返回obj

运行时环境

process-context库提供:

表达式 用途
(command-line) 返回命令行参数的不可变字符串列表,首个为命令名
(exit) 运行afterwind后退出程序并告诉操作系统正常结束
(exit obj) 运行afterwind后退出程序并告诉操作系统正常结束(若obj为#t)、异常结束(若obj为#f)或与obj有关的返回码
`(emergency-exit )退出程序并告诉操作系统正常结束
(emergency-exit obj) 退出程序并告诉操作系统正常结束(若obj为#t)、异常结束(若obj为#f)或与obj有关的返回码
(get-environment-variable name) 返回字符串name指定的环境变量值不可变字符串,没有则返回#f
(get-environment-variables) 返回一个不可变列表,其中元素为由环境变量名(不可变字符串)与其值(不可变字符串)组成的序偶。

杂项

表达式 用途
(features) 返回实现的特性标识符列表
特性标识符 含义
r7rs 此实现支持R7RS
exact-closed /外的算术运算对精确输入给出精确结果
exact-complex 提供精确的复数
ieee-float 非精确数为IEEE 754浮点数
full-unicode 支持Unicode 6.0中全部字符
ratios /对精确输入(除数非零)给出精确结果
posix 运行于POSIX系统
windows 运行于Windows
unix, darwin, gnu-linux, bsd, freebsd, solaris, … 操作系统(可多于一个)
i386, x86-64, ppc, sparc, jvm, clr, llvm, … 机器架构
ilp32, lp64, ilp64, … C内存模型
big-endian, little-endian 字节序
实现名称 实现名称