7. 对象操作

本章介绍了 Chez Scheme 中针对非数值对象的特定操作,包括 pairs, numbers 等标准对象,以及 boxes, records 等 Chez Scheme 扩展对象。第 8 章介绍了针对数字的操作。关于对象的标准操作的更多描述,参见“The Scheme Programming Language,第 4 版”的第 6 章,或 R6RS.

7.1. R6RS 中缺少的类型谓词

过程: (enum-set? obj)

返回: 如果 objenum set 则为 #t, 否则为 #f.

库: (chezscheme)

此一谓词应当被定义,但缺失于 R6RS 中。

过程: (record-constructor-descriptor? obj)

返回: 如果 objrecord constructor 则为 #t, 否则为 #f.

库: (chezscheme)

此一谓词应当被定义,但缺失于 R6RS 中。

7.2. 点对和列表 (Pair and List)

过程: (atom? obj)

返回: 如果 obj 不是点对,则为 #t, 否则为 #f.

库: (chezscheme)

atom? 等价于 (lambda (x) (not (pair? x))).

(atom? '(a b c)) => #f
(atom? '(3 . 4)) => #f
(atom? '()) => #t
(atom? 3) => #t

过程: (list-head list n)

返回: 列表前 n 个元素的列表

库: (chezscheme)

n 须为一精确非负整数,且小于或等于列表的长度。

list-head 可以和另一个 Scheme 标准过程 list-tail 配合,用于将一个列表分割成两个单独的列表。不过, list-tail 并不进行空间分配,只是返回原列表的子列表,而 list-head 则总是返回原列表前面部分的一份拷贝。

list-head 可依如下定义:

(define list-head
  (lambda (ls n)
    (if (= n 0)
        '()
        (cons (car ls) (list-head (cdr ls) (- n 1))))))

(list-head '(a b c) 0) => ()
(list-head '(a b c) 2) => (a b)
(list-head '(a b c) 3) => (a b c)
(list-head '(a b c . d) 2) => (a b)
(list-head '(a b c . d) 3) => (a b c)
(list-head '#1=(a . #1#) 5) => (a a a a a)

过程: (last-pair list)

返回: 列表的最后一个点对

库: (chezscheme)

列表不可为空。 last-pair 返回列表的最后一个点对(不是最后一个元素)。列表可以是一个不严格的列表(improper list),此时,最后一个点对为一个包含最末元素和结束对象的点对。

(last-pair '(a b c d)) => (d)
(last-pair '(a b c . d)) => (c . d)

过程: (list-copy list)

返回: 列表的一份拷贝

库: (chezscheme)

list-copy 返回与列表相等( equal? )的一个列表,使用新的点对重新构成顶层的列表结构。

(list-copy '(a b c)) => (a b c)

(let ([ls '(a b c)])
  (equal? ls (list-copy ls))) => #t

(let ([ls '(a b c)])
  (let ([ls-copy (list-copy ls)])
    (or (eq? ls-copy ls)
        (eq? (cdr ls-copy) (cdr ls))
        (eq? (cddr ls-copy) (cddr ls))))) => #f

过程: (list* obj … final-obj)

返回: 由 obj ... 组成的列表,结束于 final-obj

库: (chezscheme)

list* 和 R6RS 中的 cons* 是一样的。

过程: (make-list n)

过程: (make-list n obj)

返回: n 个对象的列表

库: (chezscheme)

n 须为非负整数。如果省略 obj,则列表的元素为未指定的。

(make-list 0 '()) => ()
(make-list 3 0) => (0 0 0)
(make-list 2 "hi") => ("hi" "hi")

过程: (iota n)

返回: 从 0(包含)到 n(不包含)的整数列表

库: (chezscheme)

n 须为精确的非负整数。

(iota 0) => ()
(iota 5) => (0 1 2 3 4)

过程: (enumerate ls)

返回: 从 0(包含)到长度 ls(不包含)的整数列表。

库: (chezscheme)

(enumerate '()) => ()
(enumerate '(a b c)) => (0 1 2)
(let ([ls '(a b c)])
  (map cons ls (enumerate ls))) => ((a . 0) (b . 1) (c . 2))

过程: (remq! obj list)

过程: (remv! obj list)

过程: (remove! obj list)

返回: 列表中所有 obj 都被移除后的列表

库: (chezscheme)

这些过程与 R6RS 中的 remq, remv, 及 remove 过程类似,只是 remq!, remv!remove! 使用输入列表中的点对来构成输出列表。它们进行较少的空间分配,但并不一定比它们非破坏性的相应版本更快。如果滥用,很容易导致混乱或错误的结果。

(remq! 'a '(a b a c a d)) => (b c d)
(remv! #\a '(#\a #\b #\c)) => (#\b #\c)
(remove! '(c) '((a) (b) (c))) => ((a) (b))

过程: (substq new old tree)

过程: (substv new old tree)

过程: (subst new old tree)

过程: (substq! new old tree)

过程: (substv! new old tree)

过程: (subst! new old tree)

返回: old 被替换为 new 后的树

库: (chezscheme)

这些过程遍历树,以对象 new 替换树中所有与对象 old 相等的对象。

对于 substqsubstq! ,相等性测试是基于 eq? , substvsubstv! 是基于 eqv?, 而 substsubst! 是基于 equal?.

substq!, substv!, 和 subst! 执行破坏性的替换。它们进行较少的空间分配,但并不一定比它们非破坏性的对应版本更快。如果滥用,很容易导致混乱或错误的结果。

(substq 'a 'b '((b c) b a)) => ((a c) a a)

(substv 2 1 '((1 . 2) (1 . 4) . 1)) => ((2 . 2) (2 . 4) . 2)

(subst 'a
       '(a . b)
       '((a . b) (c a . b) . c)) => (a (c . a) . c)

(let ([tr '((b c) b a)])
  (substq! 'a 'b tr)
  tr) => ((a c) a a)

过程: (reverse! list)

返回: 对原列表反向排序的列表

库: (chezscheme)

reverse! 通过反转其链接破坏性地反向排序列表。以 reverse! 取代 reverse 减少了空间分配,但并不一定比使用 reverse 更快。如果滥用,会很容易导致混乱或错误的结果。

(reverse! '()) => ()
(reverse! '(a b c)) => (c b a)

(let ([x '(a b c)])
  (reverse! x)
  x) => (a)

(let ([x '(a b c)])
  (set! x (reverse! x))
  x) => (c b a)

过程: (append! list …)

返回: 输入列表的串联

库: (chezscheme)

如同 append, append! 返回一个新的列表,其中元素依次为第一个列表中的元素,第二个列表中的元素,第三个列表中的元素,等等。不同之处在于, append! 重用所有参数中的点对以构造新列表。即,每一个列表参数的最后一个 cdr, 其后一元素变为指向下一个列表参数。除最后一个参数外,如果任一参数为空列表,它实质上会被忽略。最后一个参数(并不一定得是列表)是不变的。

相比于 append, append! 进行更少的空间分配,但并不一定更快。如果滥用,会很容易导致混乱或错误的结果。

(append! '(a b) '(c d)) => (a b c d)

(let ([x '(a b)])
  (append! x '(c d))
  x) => (a b c d)

7.3. 字符 (Characters)

Chez Scheme 以两种方式扩展了字符的语法。其一,前缀 #\ 后面紧跟 3 位八进制数字会被读取为一个字符,其数字编码即为此 3 位数的八进制值,例如, #\044 被读取为 #\$. 其二,它可以识别若干非标准命名的字符: #\rubout (等同于 #\delete), #\bel (等同于 #\alarm), #\vt (等同于 #\vtab), #\nel (Unicode NEL 字符), 以及 #\ls (Unicode LS 字符). 非标准字符的名字可以通过过程 char-name 更改(参见 9.14 节)。

读取器若遇到 #!r6rs ,则会在其后的输入流中禁用这些扩展,除非在更近的位置遇到 #!chezscheme.

过程: (char=? char1 char2 …)

过程: (char<? char1 char2 …)

过程: (char>? char1 char2 …)

过程: (char<=? char1 char2 …)

过程: (char>=? char1 char2 …)

过程: (char-ci=? char1 char2 …)

过程: (char-ci<? char1 char2 …)

过程: (char-ci>? char1 char2 …)

过程: (char-ci<=? char1 char2 …)

过程: (char-ci>=? char1 char2 …)

返回: 如果关系成立,则为 #t, 否则为 #f.

库: (chezscheme)

这些谓词与 R6RS 中的对应版本是一样的,只是被扩展为接受一个以上参数,而非两个以上参数。当只传入一个参数时,这些谓词均返回 #t.

(char>? #\a) => #t
(char<? #\a) => #t
(char-ci=? #\a) => #t

过程: (char- char1 char2)

返回: char1char2 间的整数差值

库: (chezscheme)

char-char1 的整数值减去 char2 的整数值,并返回差值。后面的例子假设以字符的 ASCII 码作为其整数表示。

(char- #\f #\e) => 1

(define digit-value
  ; 根据基数 r 返回数字 c 的值,
  ; 如果 c 不是有效的数字,则返回 #f
  (lambda (c r)
    (let ([v (cond
              [(char<=? #\0 c #\9) (char- c #\0)]
              [(char<=? #\A c #\Z) (char- c #\7)]
              [(char<=? #\a c #\z) (char- c #\W)]
              [else 36])])
      (and (fx< v r) v))))
(digit-value #\8 10) => 8
(digit-value #\z 10) => #f
(digit-value #\z 36) => 35

char- 可依如下定义。

(define char-
  (lambda (c1 c2)
    (- (char->integer c1) (char->integer c2))))

7.4. 字符串 (Strings)

基于标准的字符串语法, Chez Scheme 增加了两种转义字符: \' 生成单引号字符,以及 \nnn, 即,反斜杠紧跟着 3 位 8 进制数,生成等同于此 3 位 8 进制数的值的字符。读取器若遇到 #!r6rs ,则会在其后的输入流中禁用这些扩展,除非在更近的位置遇到 #!chezscheme.

所有字符串默认是可变的,包括常量。程序可以通过 string->immutable-string 创建不可变字符串。尝试修改不可变字符串会导致抛出异常。

Chez Scheme 中,字符串的长度和索引总是 fixnum

过程: (string=? string1 string2 string3 …)

过程: (string<? string1 string2 string3 …)

过程: (string>? string1 string2 string3 …)

过程: (string<=? string1 string2 string3 …)

过程: (string>=? string1 string2 string3 …)

过程: (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 …)

返回: 如果关系成立,则为 #t, 否则为 #f.

库: (chezscheme)

这些谓词与 R6RS 中的对应版本是一样的,只是被扩展为接受一个以上参数,而非两个以上参数。当只传入一个参数时,这些谓词均返回 #t.

(string>? "a") => #t
(string<? "a") => #t
(string-ci=? "a") => #t

过程: (string-copy! src src-start dst dst-start n)

返回: 未定义

库: (chezscheme)

srcdst 必须是字符串,且 dst 必须可变。 src-start, dst-start, 以及 n 必须是精确的非负整数。 src-startn 的和绝对不能超过 src 的长度, 而 dst-startn 的和则一定不能超过 dst 的长度。

string-copy!src 中起始于 src-start ,长度为 n 字节的部分,覆盖 dst 中起始于 dst-start ,长度为 n 字节的部分。即使 dstsrc 是同一个字符串,且源和目标位置相互重叠,这个操作也能生效。即,在操作开始时,目标位置先被源字符串中的字符填充。

(define s1 "to boldly go")
(define s2 (make-string 10 #\-))

(string-copy! s1 3 s2 1 3)
s2 => "-bol------"

(string-copy! s1 7 s2 4 2)
s2 => "-bolly----"

(string-copy! s2 2 s2 5 4)
s2 => "-bollolly-"

过程: (substring-fill! string start end char)

返回: 未定义

库: (chezscheme)

string 必须是可变的。 stringstart (包含) 和 end (不包含) 之间的字符均被设置为 char. startend 必须是非负整数; start 必须严格小于 string 的长度,而 end 可以小于或等于 string 的长度。如果 end ≤ start, 则字符串保持不变。

(let ([str (string-copy "a tpyo typo")])
  (substring-fill! str 2 6 #\X)
  str) => "a XXXX typo"

过程: (string-truncate! string n)

返回: 字符串或空字符串

库: (chezscheme)

string 必须是可变的。 n 必须是精确的非负 fixnum ,且不大于 string 的长度。如果 n 是 0, string-truncate! 返回空字符串。否则, string-truncate! 破坏性地把 string 缩短为其前 n 个字符,并返回 string.

(define s (make-string 7 #\$))
(string-truncate! s 0) => ""
s => "$$$$$$$"
(string-truncate! s 3) => "$$$"
s => "$$$"

过程: (mutable-string? obj)

返回: 如果 obj 是可变字符串,则为 #t, 否则为 #f.

过程: (immutable-string? obj)

返回: 如果 obj 是不可变字符串,则为 #t, 否则为 #f.

库: (chezscheme)

(mutable-string? (string #\a #\b #\c)) => #t
(mutable-string? (string->immutable-string "abc")) => #f
(immutable-string? (string #\a #\b #\c)) => #f
(immutable-string? (string->immutable-string "abc")) => #t
(immutable-string? (cons 3 4)) => #f

过程: (string->immutable-string string)

返回: 与 string 相等(equal)的不可变字符串

库: (chezscheme)

如果 string 是不可变字符串,则结果为其本身;否则,结果是个不可变字符串,其内容与 string 相同。

(define s (string->immutable-string (string #\x #\y #\z)))
(string-set! s 0 #\a) => exception: not mutable

7.5. 向量 (Vectors)

Chez Scheme 扩展了向量的语法,以允许在 # 和左括号之间指定向量的长度,形如, #3(a b c). 如果在此语法形式下提供的向量元素比指定的长度要少,则之后的每个元素都与最后一个提供的元素相同。读取器若遇到 #!r6rs ,则会在其后的输入流中禁用这些扩展,除非在更近的位置遇到 #!chezscheme.

Chez Scheme 中,向量的长度和索引总是 fixnum

所有向量默认都是可变的,包括常量。程序可以通过 vector->immutable-vector 创建不可变向量。尝试修改不可变向量会导致抛出异常。

过程: (vector-copy vector)

返回: vector 的一份拷贝

库: (chezscheme)

vector-copy 生成一个长度和内容都和 vector 相同的新向量。里面的元素本身不是被复制的。

(vector-copy '#(a b c)) => #(a b c)

(let ([v '#(a b c)])
  (eq? v (vector-copy v))) => #f

过程: (vector-set-fixnum! vector n fixnum)

返回: 未定义

库: (chezscheme)

vector 必须是不可变的。 vector-set-fixnum! 把向量的第 n 个元素变更为 fixnum. n 必须是一个确切的非负整数,且严格小于 vector 的长度。

储存 fixnum 要比储存任意值快,因为对于任意值,系统需要记录从老到新的各个对象的潜在分配,以支持分代垃圾回收。不过,必须小心确保参数确实是一个 fixnum ;否则,收集器可能无法正确地追踪资源分配。只要不在优化级别 3, 基本过程会对参数进行 fixnum 检验。

参见后面关于 fixnum-only vectors (fxvectors) 的描述。

(let ([v (vector 1 2 3 4 5)])
  (vector-set-fixnum! v 2 73)
  v) => #(1 2 73 4 5)

过程: (vector-cas! vector n old-obj new-obj)

返回: 如果 vector 有改变,则为 #t, 否则为 #f.

库: (chezscheme)

vector 必须是可变的。 若 vector 的第 n 个元素和 old-obj 相同( eq? ), 则 vector-cas! 自动将此元素替换为 new-obj, 若不相同,则 vector 保持不变。

(define v (vector 'old0 'old1 'old2))
(vector-cas! v 1 'old1 'new1) => #t
(vector-ref v 1) => 'new1
(vector-cas! v 2 'old1 'new2) => #f
(vector-ref v 2) => 'old2

过程: (mutable-vector? obj)

返回: 如果 obj 是可变向量,则为 #t, 否则为 #f.

过程: (immutable-vector? obj)

返回: 如果 obj 是不可变向量,则为 #t, 否则为 #f.

库: (chezscheme)

(mutable-vector? (vector 1 2 3)) => #t
(mutable-vector? (vector->immutable-vector (vector 1 2 3))) => #f
(immutable-vector? (vector 1 2 3)) => #f
(immutable-vector? (vector->immutable-vector (vector 1 2 3))) => #t
(immutable-vector? (cons 3 4)) => #f

过程: (vector->immutable-vector vector)

返回: 与 vector 相等(equal)的一个不可变向量

库: (chezscheme)

如果 vector 是不可变向量,则结果为其本身;否则,结果是与 vector 内容相同的一个不可变向量。

(define v (vector->immutable-vector (vector 1 2 3)))
(vector-set! v 0 0) => exception: not mutable

7.6. Fixnum-Only Vectors

Fixnum-only Vectors, 即 fxvectors, 类似于向量,但只包含 fixnumfxvector 的输出形式以前缀 #vfx 替换向量的前缀 #, 例如, #vfx(1 2 3)#10vfx(2). 读取器若遇到 #!r6rs ,则会在其后的输入流中禁用 fxvector 的语法,除非在更近的位置遇到 #!chezscheme.

fxvector 的长度和索引总是 fixnum

更新 fxvector 通常比更新向量节省资源,因为对于向量来说,系统需要记录从老到新的各个对象的潜在分配,以支持分代垃圾回收。 fxvector 不包含要指向内存某一区域的指针,受益于此,存储管理系统不需要在垃圾收集期间对这些指针进行追踪。

fxvector 默认是可变的,包括常量。程序可以通过 fxvector->immutable-fxvector 创建不可变的 fxvector 。尝试修改一个不可变的 fxvector 会导致异常抛出。

可参考前述的 vector-set-fixnum!.

过程: (fxvector? obj)

返回: 如果 obj 是一个 fxvector ,则为 #t, 否则为 #f.

库: (chezscheme)

(fxvector? #vfx()) => #t
(fxvector? #vfx(1 2 3)) => #t
(fxvector? (fxvector 1 2 3)) => #t
(fxvector? '#(a b c)) => #f
(fxvector? '(a b c)) => #f
(fxvector? "abc") => #f

过程: (fxvector fixnum …)

返回: 一个由参数中的所有 fixnum fixnum ... 组成的 fxvector

库: (chezscheme)

(fxvector) => #vfx()
(fxvector 1 3 5) => #vfx(1 3 5)

过程: (make-fxvector n)

过程: (make-fxvector n fixnum)

返回: 一个长度为 nfxvector

库: (chezscheme)

n 必须是 fixnum 。如果有提供参数 fixnum, 则 fxvector 中的每个元素都被初始化为 fixnum; 不然,其中元素则均为未定义。

(make-fxvector 0) => #vfx()
(make-fxvector 0 7) => #vfx()
(make-fxvector 5 7) => #vfx(7 7 7 7 7)

过程: (fxvector-length fxvector)

返回: fxvector 中的元素个数

库: (chezscheme)

(fxvector-length #vfx()) => 0
(fxvector-length #vfx(1 2 3)) => 3
(fxvector-length #10vfx(1 2 3)) => 10
(fxvector-length (fxvector 1 2 3 4)) => 4
(fxvector-length (make-fxvector 300)) => 300

过程: (fxvector-ref fxvector n)

返回: fxvector 中的第 n 个元素 (索引基于 0)

库: (chezscheme)

n 必须是一个非负 fixnum ,且严格小于 fxvector 的长度。

(fxvector-ref #vfx(-1 2 4 7) 0) => -1
(fxvector-ref #vfx(-1 2 4 7) 1) => 2
(fxvector-ref #vfx(-1 2 4 7) 3) => 7

过程: (fxvector-set! fxvector n fixnum)

返回: 未定义

库: (chezscheme)

fxvector 必须是可变的。 n 必须是一个非负 fixnum ,且严格小于 fxvector 的长度。 fxvector-set!fxvector 中的第 n 个元素修改为 fixnum.

(let ([v (fxvector 1 2 3 4 5)])
  (fxvector-set! v 2 (fx- (fxvector-ref v 2)))
  v) => #vfx(1 2 -3 4 5)

过程: (fxvector-fill! fxvector fixnum)

返回: 未定义

库: (chezscheme)

fxvector 必须是可变的。 fxvector-fill!fxvector 中的每个元素替换为 fixnum.

(let ([v (fxvector 1 2 3)])
  (fxvector-fill! v 0)
  v) => #vfx(0 0 0)

过程: (fxvector->list fxvector)

返回: fxvector 中所有元素组成的列表

库: (chezscheme)

(fxvector->list (fxvector)) => ()
(fxvector->list #vfx(7 5 2)) => (7 5 2)

(let ([v #vfx(1 2 3 4 5)])
  (apply fx* (fxvector->list v))) => 120

过程: (list->fxvector list)

返回: list 中所有元素组成的 fxvector

库: (chezscheme)

list 必须完全由 fixnum 组成。

(list->fxvector '()) => #vfx()
(list->fxvector '(3 5 7)) => #vfx(3 5 7)

(let ([v #vfx(1 2 3 4 5)])
  (let ([ls (fxvector->list v)])
    (list->fxvector (map fx* ls ls)))) => #vfx(1 4 9 16 25)

过程: (fxvector-copy fxvector)

返回: fxvector 的一份拷贝

库: (chezscheme)

fxvector-copy 生成一个与 fxvector 长度和内容都一样的新的 fxvector

(fxvector-copy #vfx(3 4 5)) => #vfx(3 4 5)

(let ([v #vfx(3 4 5)])
  (eq? v (fxvector-copy v))) => #f

过程: (mutable-fxvector? obj)

返回: 如果 obj 是一个可变的 fxvector ,则为 #t, 否则为 #f.

过程: (immutable-fxvector? obj)

返回: 如果 obj 是一个不可变的 fxvector ,则为 #t, 否则为 #f.

库: (chezscheme)

(mutable-fxvector? (fxvector 1 2 3)) => #t
(mutable-fxvector? (fxvector->immutable-fxvector (fxvector 1 2 3))) => #f
(immutable-fxvector? (fxvector 1 2 3)) => #f
(immutable-fxvector? (fxvector->immutable-fxvector (fxvector 1 2 3))) => #t
(immutable-fxvector? (cons 3 4)) => #f

过程: (fxvector->immutable-fxvector fxvector)

返回: fxvector 的一份不可变的拷贝或其自身

库: (chezscheme)

如果 fxvector 是不可变的,则结果为其本身;否则,结果是与 fxvector 内容相同的一个不可变 fxvector

(define v (fxvector->immutable-fxvector (fxvector 1 2 3)))
(fxvector-set! v 0 0) => exception: not mutable

7.7. 字节向量 (Bytevectors)

如同向量, Chez Scheme 也扩展了字节向量的语法,以允许在 # 和左括号之间指定向量的长度,例如, #3vu8(1 105 73). 如果在此语法形式下提供的向量元素比指定的长度要少,则之后的每个元素都与最后一个提供的元素相同。读取器若遇到 #!r6rs ,则会在其后的输入流中禁用这些扩展,除非在更近的位置遇到 #!chezscheme.

Chez Scheme 同时扩展了字节向量的基本操作集,包括了加载和存储 3, 5, 6, 7 字节长度的基本操作。

Chez Scheme 中,字节向量的长度和索引总是 fixnum

字节向量默认是可变的,包括常量。程序可以通过 bytevector->immutable-bytevector 创建不可变字节向量。尝试修改不可变字节向量会导致抛出异常。

过程: (bytevector fill …)

返回: 一个内容为 fill ... 的字节向量

库: (chezscheme)

每个填充值必须为一个表示 8 位有符号或无符号值的精确整数,即,一个在范围 -128 至 255(两端均包含)之间的值。负的填充值被视为与其等价的补码。

(bytevector) => #vu8()
(bytevector 1 3 5) => #vu8(1 3 5)
(bytevector -1 -3 -5) => #vu8(255 253 251)

过程: (bytevector->s8-list bytevector)

返回: bytevector 转化成的 8 位有符号数列表

库: (chezscheme)

返回列表中的值均为精确的 8 位有符号整数,即,在范围 -128 至 127(两端均包含)之间的值。 bytevector->s8-list 与 R6RS 中的 bytevector->u8-list 相似,只是返回列表中的值是有符号数,而非无符号数。

(bytevector->s8-list (make-bytevector 0)) => ()
(bytevector->s8-list #vu8(1 127 128 255)) => (1 127 -128 -1)

(let ([v #vu8(1 2 3 255)])
  (apply * (bytevector->s8-list v))) => -6

过程: (s8-list->bytevector list)

返回: list 中的元素组成的新字节向量

库: (chezscheme)

list 必须完全由精确的 8 位有符号整数组成,即,在范围 -128 至 127(两端均包含)之间的值。 s8-list->bytevector 与 R6RS 中的过程 u8-list->bytevector 相似,只是输入列表中的元素是有符号数,而非无符号数。

(s8-list->bytevector '()) => #vu8()
(s8-list->bytevector '(1 127 -128 -1)) => #vu8(1 127 128 255)

(let ([v #vu8(1 2 3 4 5)])
  (let ([ls (bytevector->s8-list v)])
    (s8-list->bytevector (map - ls)))) => #vu8(255 254 253 252 251)

过程: (bytevector-truncate! bytevector n)

返回: bytevector, 或空字节向量

库: (chezscheme)

bytevector 必须是可变的。 n 必须是一个精确的非负 fixnum ,且不大于 bytevector 的长度。如果 n 是 0, bytevector-truncate! 返回空字节向量,否则, bytevector-truncate! 破坏性地把 bytevector 缩短为其前 n 个字节,并返回 bytevector.

(define bv (make-bytevector 7 19))
(bytevector-truncate! bv 0) => #vu8()
bv => #vu8(19 19 19 19 19 19 19)
(bytevector-truncate! bv 3) => #vu8(19 19 19)
bv => #vu8(19 19 19)

过程: (bytevector-u24-ref bytevector n eness)

返回: bytevector 的索引 n (始于 0) 处的 24 位无符号整数

过程: (bytevector-s24-ref bytevector n eness)

返回: bytevector 的索引 n (始于 0) 处的 24 位有符号整数

过程: (bytevector-u40-ref bytevector n eness)

返回: bytevector 的索引 n (始于 0) 处的 40 位无符号整数

过程: (bytevector-s40-ref bytevector n eness)

返回: bytevector 的索引 n (始于 0) 处的 40 位有符号整数

过程: (bytevector-u48-ref bytevector n eness)

返回: bytevector 的索引 n (始于 0) 处的 48 位无符号整数

过程: (bytevector-s48-ref bytevector n eness)

返回: bytevector 的索引 n (始于 0) 处的 48 位有符号整数

过程: (bytevector-u56-ref bytevector n eness)

返回: bytevector 的索引 n (始于 0) 处的 56 位无符号整数

过程: (bytevector-s56-ref bytevector n eness)

返回: bytevector 的索引 n (始于 0) 处的 56 位有符号整数

库: (chezscheme)

n 必须是一个精确的非负整数,并且指示数值的起始字节。 n 和数值占用的字节之和 (24 位数值为 3 个字节,40 位数值为 5 个字节,48 位数值为 6 个字节,56 位数值为 7 个字节) 一定不能超过 bytevector 的长度。 eness 必须是一个命名此字节序(endianness)的有效的字节序符号(symbol)。

返回值是一个精确整数,根据数值所占用的字节数处于适当的区间。有符号数是作为其补码存储的等价值。

过程: (bytevector-u24-set! bytevector n u24 eness)

过程: (bytevector-s24-set! bytevector n s24 eness)

过程: (bytevector-u40-set! bytevector n u40 eness)

过程: (bytevector-s40-set! bytevector n s40 eness)

过程: (bytevector-u48-set! bytevector n u48 eness)

过程: (bytevector-s48-set! bytevector n s48 eness)

过程: (bytevector-u56-set! bytevector n u56 eness)

过程: (bytevector-s56-set! bytevector n s56 eness)

返回: 未定义

库: (chezscheme)

bytevector 必须是不可变的。 n 必须是一个精确的非负整数,并且指示数值的起始字节。 n 和数值占用的字节之和一定不能超过 bytevector 的长度。 u24 必须是一个 24 位无符号值,即,在 0 至 224 - 1 (两端均包含) 区间中的值; s24 必须是一个 24 位有符号值,即,在 -223 至 223 - 1 (两端均包含) 区间中的值; u40 必须是一个 40 位无符号值,即,在 0 至 240 - 1 (两端均包含) 区间中的值; s40 必须是一个 40 位有符号值,即,在 -239 至 239 - 1 (两端均包含) 区间中的值; u48 必须是一个 48 位无符号值,即,在 0 至 248 - 1 (两端均包含) 区间中的值; s48 必须是一个 48 位有符号值,即,在 -247 至 247 - 1 (两端均包含) 区间中的值; u56 必须是一个 56 位无符号值,即,在 0 至 256 - 1 (两端均包含) 区间中的值; s56 必须是一个 56 位有符号值,即,在 -255 至 255 - 1 (两端均包含) 区间中的值。 eness 必须是一个命名此字节序(endianness)的有效的字节序符号(symbol)。

这些过程把给定值存储到 bytevector 的索引 n (基于 0)处起始的 3, 5, 6, 7 字节中。负值存储为与其等价的补码。

过程: (mutable-bytevector? obj)

返回: 如果 obj 是一个可变的字节向量,则为 #t, 否则为 #f.

过程: (immutable-bytevector? obj)

返回: 如果 obj 是一个不可变的字节向量,则为 #t, 否则为 #f.

库: (chezscheme)

(mutable-bytevector? (bytevector 1 2 3)) => #t
(mutable-bytevector?
 (bytevector->immutable-bytevector (bytevector 1 2 3))) => #f
(immutable-bytevector? (bytevector 1 2 3)) => #f
(immutable-bytevector?
 (bytevector->immutable-bytevector (bytevector 1 2 3))) => #t
(immutable-bytevector? (cons 3 4)) => #f

过程: (bytevector->immutable-bytevector bytevector)

返回: 与 bytevector 相等(equal)的一个不可变字节向量

库: (chezscheme)

如果 bytevector 是不可变的,则结果为其本身;否则,结果是与 bytevector 内容相同的一个不可变字节向量。

(define bv (bytevector->immutable-bytevector (bytevector 1 2 3)))
(bytevector-u8-set! bv 0 0) => exception: not mutable

过程: (bytevector-compress bytevector)

返回: 一个新的字节向量,包含 bytevector 压缩后的内容

库: (chezscheme)

结果是原始的压缩数据,以最精简的头部信息记录了压缩前的大小和压缩模式。结果并未包含使用压缩选项通过 port-based 压缩所写入的头部信息。

过程: (bytevector-uncompress bytevector)

返回: 一个字节向量,包含 bytevector 解压缩后的内容

库: (chezscheme)

把一个由 bytevector-compress 生成的 bytevector, 解压为一个新的字节向量,其内容与最初传递给 bytevector-compress 的 字节向量相同。

7.8. Boxes

Boxes 是一种单元素对象,主要用于提供一个“额外的间接层”。这个额外的间接层,通常用于使多个代码块或数据结构可以共享指向一个对象的引用,或指针。例如,在采用此种参数传递规则的语言的解释器中,可以用 boxes 实现 call-by-reference 的语义。

Boxes 的字面形式带有前缀 #& (发音为 "hash-ampersand"). 例如, #&(a b c) 是一个 box,内容为列表 (a b c). 读取器若遇到 #!r6rs ,则会在其后的输入流中禁用 box 语法,除非在更近的位置遇到 #!chezscheme.

所有 boxes 默认是可变的,包括常量。程序可以通过 box-immutable 创建不可变 boxes. 尝试修改不可变 box 会导致抛出异常。

过程: (box? obj)

返回: 如果 objbox, 则为 #t, 否则为 #f.

库: (chezscheme)

(box? '#&a) => #t
(box? 'a) => #f
(box? (box 3)) => #t

过程: (box obj)

返回: 一个包含 obj 的新的 box

库: (chezscheme)

(box 'a) => #&a
(box (box '(a b c))) => #&#&(a b c)

过程: (unbox box)

返回: box 的内容

库: (chezscheme)

(unbox #&a) => a
(unbox #&#&(a b c)) => #&(a b c)

(let ([b (box "hi")])
  (unbox b)) => "hi"

过程: (set-box! box obj)

返回: 未定义

库: (chezscheme)

box 必须是可变的。 set-box!box 的内容设置为 obj.

(let ([b (box 'x)])
  (set-box! b 'y)
  b) => #&y

(let ([incr!
       (lambda (x)
         (set-box! x (+ (unbox x) 1)))])
  (let ([b (box 3)])
    (incr! b)
    (unbox b))) => 4

过程: (box-cas! box old-obj new-obj)

返回: 如果 box 被改变,则为 #t, 否则为 #f.

库: (chezscheme)

box 必须是可变的。 若 box 待替换的内容和 old-obj 相同(基于 eq?), 则 box-cas! 自动将 box 的内容替换为 new-obj; 若不相同,则 box 保持不变。

(define b (box 'old))
(box-cas! b 'old 'new) => #t
(unbox b) => 'new
(box-cas! b 'other 'wrong) => #f
(unbox b) => 'new

过程: (mutable-box? obj)

返回: 如果 obj 是可变的 box ,则为 #t, 否则为 #f.

过程: (immutable-box? obj)

返回: 如果 obj 是不可变的 box ,则为 #t, 否则为 #f.

库: (chezscheme)

(mutable-box? (box 1)) => #t
(mutable-box? (box-immutable 1)) => #f
(immutable-box? (box 1)) => #f
(immutable-box? (box-immutable 1)) => #t
(mutable-box? (cons 3 4)) => #f

过程: (box-immutable obj)

返回: 一个内容为 obj 的新的不可变 box

库: (chezscheme)

Boxes 通常用来支持共享的,可变的结构,所以不可变的 box 一般没什么用。

(define b (box-immutable 1))
(set-box! b 0) => exception: not mutable

7.9. 符号 (Symbols)

Chez Scheme 对标准符号语法进行了多方面的扩展:

符号名可以以 @ 开始,但是, ,@abc 被解析为 (unquote-splicing abc); 如果要生成 (unquote @abc) ,可以键入 , @abc, \x40;abc, 或者 ,|@abc|.

单字符 {} 被读取为符号。

通常作为数字起始的任意字符,可以作为符号名的开头,包括数字, ., +, -, 只要此字符和其后的字符序列总体不会被解析成一个数字就可以。

名字包含任意字符的符号可以通过转义符 \| 来书写。 \ 用来转义单个字符(除了 'x',因为 \x 是 16 进制数的起始标记),而 | 用来转义一组字符,直到配对的 | 为止。

打印器总是依标准 R6RS 语法打印符号,所以,如 @abc 会被打印为 \x40;abc, 1- 会被打印为 \x31;-.

gensym 过程族把“美观”版和“唯一”版名字放到 #{} 之间打印出来,形如 #{g1426 e5g1c94g642dssw-a}. 它们也可以以前缀 #: 来只打印美观版名字,如 #:g1426.

读取器若遇到 #!r6rs ,则会在其后的输入流中禁用这些扩展,除非在更近的位置遇到 #!chezscheme.

过程: (gensym)

过程: (gensym pretty-name)

过程: (gensym pretty-name unique-name)

返回: 一个唯一生成的符号

库: (chezscheme)

每个 gensym 调用返回一个唯一生成的符号,即 gensym. 每个生成的符号有两个名字:一个“美观”版名字和一个“唯一”版名字。

在上文的第一个形式中,美观名字是通过组合一个内部的前缀和一个内部计数器的值来形成(惰性的──参见下文)。在每个名字形成之后,内部计数器会增加。下文将提及的参数 gensym-prefixgensym-count, 可以用来访问和设置内部前缀和计数器。前缀默认为单字符字符串 "g". 在第二和第三种形式中,新的 gensym 的美观名字是 pretty-name, 其必须为一字符串。 gensym 的美观名字由过程 symbol->string 返回。

在前两种形式中,唯一名字是一个自动生成的全局唯一名字。全局唯一名字是通过组合一个全局唯一标识符和一个内部计数器来构造的(惰性的──参见下文)。在第三种 gensym 的形式中,新 gensym 的唯一名字是 unique-name, 其必须为一字符串。 gensym 的唯一名字可以通过过程 gensym->unique-string 获得。

唯一名字使得 gensym 的字面形式可以被读回及在输入端可靠地一般化。 gensym 的语法包括美观名字和唯一名字,如下面的例子所示:

(gensym) => #{g0 bcsfg5eq4e9b3h9o-a}

当参数对象 print-gensym 被设为 pretty, 则打印器只输出美观名字,使用 #: 语法,所以

(parameterize ([print-gensym 'pretty])
  (write (gensym)))

输出 #:g0.

当读取器遇到 #: 语法,便以其提供的美观名字产生一个 gensym ,但是最初的唯一名字就遗失了。

当参数对象被设为 #f, 则打印器只输出美观名字,所以

(parameterize ([print-gensym #f])
  (write (gensym)))

输出 g0. 只有在 gensym 不需要再作为 gensym 被读取回来时,这种做法才有益。

gensym 被频繁创建,但很少被输出或存储在一个对象文件中时,为了减小构造和(线程)同步消耗,美观和唯一名字的生成是惰性的,即,直到第一次被打印器, fasl writer, 或显式地被过程 symbol->stringgensym->unique-string 请求时才创建。此外,在唯一名字被请求之前, gensym 不会被放入系统的驻留符号表(oblist; 参见第 156 页)。这使得一个 gensym 可以在下列情况下被存储管理器回收:如果不存在对这个 gensym 的引用,且不存在可访问的唯一名字,即使它有一个顶层绑定,或非空的属性列表。

(define x (gensym))
x                         => #{g2 bcsfg5eq4e9b3h9o-c}
(symbol->string x)        => "g2"
(gensym->unique-string x) => "bcsfg5eq4e9b3h9o-c"

gensym 包含之前版本 Chez Scheme 支持的非驻留符号 (uninterned symbols) 的概念。同样,谓词 gensym? 也取代了 uninterned-symbol?.

thread parameter: gensym-prefix

thread parameter: gensym-count

库: (chezscheme)

gensym 没有传入显式的字符串参数时,参数对象 gensym-prefixgensym-count 被用来访问和设置生成美观名字的内部前缀和计数器。 gensym-prefix 默认为字符串 "g" , 并且可以被设置为任何对象。 gensym-count 起始于 0, 并且可以被设置为任何非负整数。

如上所述, Chez Scheme 把美观名字的创建延迟到其第一次被请求时──被打印器或通过显式调用 symbol->string 。在那之前,这些参数对象并不会起作用;因此,在调用 gensym 时设置它们,对生成的名字并没有影响。

(let ([x (parameterize ([gensym-prefix "genny"]
                        [gensym-count 17]
                        [print-gensym 'pretty])
           (gensym))])
  (format "~s" x))                       => "#{g4 bcsfg5eq4e9b3h9o-e}"
(let ([x (gensym)])
  (parameterize ([gensym-prefix "genny"]
                 [gensym-count 17]
                 [print-gensym #f])
    (format "~s" (gensym))))             => "genny17"

过程: (gensym->unique-string gensym)

返回: gensym 的唯一名字

库: (chezscheme)

(gensym->unique-string (gensym)) => "bd3kufa7ypjcuvut-g"

过程: (gensym? obj)

返回: 如果 objgensym ,则为 #t, 否则为 #f.

库: (chezscheme)

(gensym? (string->symbol "z")) => #f
(gensym? (gensym "z")) => #t
(gensym? 'a) => #f
(gensym? 3) => #f
(gensym? (gensym)) => #t
(gensym? '#{g2 bcsfg5eq4e9b3h9o-c}) => #t

过程: (putprop symbol key value)

返回: 未定义

库: (chezscheme)

Chez Scheme 给每个符号关联一个属性列表,允许多个键值对直接和符号存储在一起。使用过程 putpropgetprop, 可以把新的键值对存入属性列表,或以类似关联列表的使用方式获取值。属性列表通常用来存储与符号自身相关的信息。比如,一个自然语言程序,可以使用符号来表示单词,利用它们的属性列表来存储有关用法和词意的信息。

putprop 在符号的属性列表中把键值关联起来。虽然键通常是符号,但键和值实际可以是任意类型的对象。

putprop 可以用来创建新属性,或改变已有属性。

参见 getprop 后面的例子。

过程: (getprop symbol key)

过程: (getprop symbol key default)

返回: symbol 属性列表中与 key 关联的值

库: (chezscheme)

getpropsymbol 的属性列表中搜索,寻找与 key 相同 (基于 eq?) 的键,如果存在,则返回与此键关联的值。如果在 symbol 的属性列表中,没有与 key 相关联的值, getprop 返回 default, 若没有提供 default 参数,则返回 #f.

(putprop 'fred 'species 'snurd)
(putprop 'fred 'age 4)
(putprop 'fred 'colors '(black white))

(getprop 'fred 'species) => snurd
(getprop 'fred 'colors) => (black white)
(getprop 'fred 'nonkey) => #f
(getprop 'fred 'nonkey 'unknown) => unknown

(putprop 'fred 'species #f)
(getprop 'fred 'species 'unknown) => #f

过程: (remprop symbol key)

返回: 未定义

库: (chezscheme)

如果键 key 对应的属性存在,则 rempropsymbol 的属性列表中移除此属性。

(putprop 'fred 'species 'snurd)
(getprop 'fred 'species) => snurd

(remprop 'fred 'species)
(getprop 'fred 'species 'unknown) => unknown

过程: (property-list symbol)

返回: symbol 内部属性列表的一份拷贝

库: (chezscheme)

属性列表是一个键和值交替排列的列表,即, (key value ...).

(putprop 'fred 'species 'snurd)
(putprop 'fred 'colors '(black white))
(property-list 'fred) => (colors (black white) species snurd)

过程: (oblist)

返回: 驻留符号列表

库: (chezscheme)

系统维护着一份驻留符号表,以确保同一符号名在任意两处出现时会被解析为同一个符号对象。 oblist 过程返回当前处于此符号表中的符号列表。

当一个新的符号被引入系统,或一个 gensym (参见 152 页)的唯一名字被请求时,驻留符号列表变长。当垃圾收集器判断可以安全地丢弃一个符号时,列表缩短。可以安全丢弃一个符号的条件为:除了通过 oblist, 在别处均无法访问此符号,符号没有顶层绑定,属性列表中没有任何属性。

(if (memq 'tiger (oblist)) 'yes 'no) => yes
(equal? (oblist) (oblist)) => #t
(= (length (oblist)) (length (oblist))) => #t or #f

上面的第一个例子体现了,所有驻留符号从它们被读取时即纳入驻留列表,早于被求值。第二个例子体现了,当对符号的引用存在时,没有符号可以从驻留列表中被移除,在当前情况下,即为对 oblist 的第一次调用(无论哪个调用先被执行)时返回的列表中的符号。第三个例子中的表达式,只有当垃圾收集发生在两次对 oblist 的调用之间时,且仅在这次收集从驻留列表中移除了一个或更多符号时,才会返回 #f.

7.10. Void

很多 Scheme 操作返回未定义结果。当一个操作的返回值为未定义时, Chez Scheme 通常返回一个特殊的 void 对象。 Chez Scheme 的 void 对象并不是要作为数据,因而也没有相应的读取器语法。就像没有读取器语法的其它对象,例如过程和端口, Chez Scheme 的输出过程以一种不可读的表示方式打印 void 对象,即, #<void>. 由于 void 对象应当只被那些并没有有意义的返回值的操作返回,所以默认的交互环境 (参见 waiter-write) 并不打印出 void 对象。 Chez Scheme 中返回 void 对象的操作有: set!, set-car!, load, and write 等等。

过程: (void)

返回: void 对象

库: (chezscheme)

void 是一个返回 void 对象的无参数过程。它可以用于强制那些只产生副作用的或值为未定义的表达式,使其求值为一个一致的,不重要的值。不过,由于多数 Chez Scheme 中用于副作用的操作本就返回 void 对象,所以几乎没有显式调用 void 过程的必要。

由于 void 对象用于显式地表示一个“未定义”值,所以不应当把它用作任何其它目的,或依赖于任何表达式会求值为 void 对象。

默认的交互环境输出会忽略 void 对象;即,值为 void 对象的表达式什么都不会打印出来。

(eq? (void) #f) => #f
(eq? (void) #t) => #f
(eq? (void) '()) => #f

7.11. 排序 (Sorting)

过程: (sort predicate list)

过程: (sort! predicate list)

返回: list 中元素根据 predicate 排序后组成的列表

库: (chezscheme)

sort 和 R6RS 中的 list-sort 是一样的,而 sort!sort 的破坏性版本,即,它重用输入列表中的点对来构造输出列表。

(sort < '(3 4 2 1 2 5)) => (1 2 2 3 4 5)
(sort! < '(3 4 2 1 2 5)) => (1 2 2 3 4 5)

过程: (merge predicate list1 list2)

过程: (merge! predicate list1 list2)

返回: 依 predicate 指定的顺序融合 list1list2.

库: (chezscheme)

predicate 应该是一个接收两个参数的过程,当它的第一个参数在融合后的列表中要排在第二个参数之前时返回 #t. 它不应有任何负作用。即,如果把 predicate 作用于两个对象 xy, x 来自于第一个列表,而 y 来自于第二个列表,它应该只在 x 应在输出列表中排在 y 之前时返回 #t. 如果满足这个限制条件, mergemerge! 就是可靠的,其中来自 list1 的项在输出列表中排在来自 list2 的相等项的前面。融合后的列表中包含重复元素。

merge! 破坏性地组合列表,使用输入列表中的点对来构造输出列表。

(merge char<?
       '(#\a #\c)
       '(#\b #\c #\d)) => (#\a #\b #\c #\c #\d)
(merge <
       '(1/2 2/3 3/4)
       '(0.5 0.6 0.7)) => (1/2 0.5 0.6 2/3 0.7 3/4)

7.12. 哈希表 (Hashtables)

Chez Schemehashtable 机制进行了多方面的扩展,包括一种直接访问 hashtable 中键值对的方法,对 weak(弱引用) eq hashtableweak eqv hashtable 的支持,及一些为 eq hashtablesymbol hashtable 定制的过程。

过程: (hashtable-cell hashtable key default)

返回: 一个点对 (参见下文)

库: (chezscheme)

hashtable 必须是一个 hashtable. keydefault 可以是任意 Scheme 值。

如果 hashtable 中没有值与 key 相关联, hashtable-cell 修改 hashtable 以使 key 关联到 default. 它返回一个点对, carkey, cdr 是关联的值。改变这个点对的 cdr 字段,事实上会更改这个表,使 key 关联到一个新值。car 字段中的 key 是不应该被改动的。相对于 R6RS 中的相应过程对 hashtable 条目的操作,这个过程的优势是,只通过一次 hashtable 查询,关联到一个键的值就可以被读写任意多次。

(define ht (make-eq-hashtable))
(define v (vector 'a 'b 'c))
(define cell (hashtable-cell ht v 3))
cell => (#(a b c) . 3)
(hashtable-ref ht v 0) => 3
(set-cdr! cell 4)
(hashtable-ref ht v 0) => 4

过程: (hashtable-values hashtable)

返回: hashtable 中的值组成的向量

库: (chezscheme)

各值是 hashtable 中各个键的值。结果中未移除重复的值。返回向量中的值的顺序是不定的。

(define ht (make-eq-hashtable))
(define p1 (cons 'a 'b))
(define p2 (cons 'a 'b))
(hashtable-set! ht p1 "one")
(hashtable-set! ht p2 "two")
(hashtable-set! ht 'q "two")
(hashtable-values ht) => #("one" "two" "two")

这个过程等价于:

(lambda (ht)
  (let-values ([(keys values) (hashtable-entries ht)])
    values))

但更高效,因为那个键向量并不需要被创建。

过程: (make-weak-eq-hashtable)

过程: (make-weak-eq-hashtable size)

过程: (make-weak-eqv-hashtable)

过程: (make-weak-eqv-hashtable size)

返回: 一个新的 weak eq hashtable

库: (chezscheme)

这些过程与 R6RS 中的过程 make-eq-hashtablemake-eqv-hashtable 是类似的,只是 hashtable 中的键是弱引用的,即,面对垃圾回收,它们并不受到保护。被垃圾收集器回收的键会从表中移除,而它们的关联值最晚会在下次表被修改时丢弃。

只要键没有被回收, hashtable 中的值就能被正常引用,因为键和值是使用弱引用点对(weak pair)配对在一起的。因此,如果 hashtable 中的一个值反向指回它自己的键,就会阻止垃圾收集器回收这个键。参见 make-ephemeron-eq-hashtablemake-ephemeron-eqv-hashtable.

通过 hashtable-copy 复制一个 weak eq hashtableweak eqv hashtable 产生的拷贝也是弱引用的。如果此拷贝是不可变的,不可访问的键仍然可以从表中被丢弃,即使表中的内容在其它情况下是不变的。这种效果可以通过 hashtable-keyshashtable-entries 来观察。

(define ht1 (make-weak-eq-hashtable))
(define ht2 (make-weak-eq-hashtable 32))

过程: (make-ephemeron-eq-hashtable)

过程: (make-ephemeron-eq-hashtable size)

过程: (make-ephemeron-eqv-hashtable)

过程: (make-ephemeron-eqv-hashtable size)

返回: 一个新的 ephemeron eq hashtable

库: (chezscheme)

这些过程类似于 make-weak-eq-hashtablemake-weak-eqv-hashtable, 但表中的值可以在(直接或间接)指向表中的键时,却不阻止垃圾收集器回收此键,因为键与值是通过 ephemeron pairs 配对的。

通过 hashtable-copy 复制一个 ephemeron eq hashtableephemeron eqv hashtable 产生的拷贝也是 ephemeron table. 一个不可访问的键从一个不可变的 ephemeron hashtable 中被丢弃的方式,和在不可变的 weak hashtable 中相同。

(define ht1 (make-ephemeron-eq-hashtable))
(define ht2 (make-ephemeron-eq-hashtable 32))

过程: (hashtable-weak? obj)

返回: 如果 objweak eq hashtableweak eqv hashtable, 则为 #t, 否则为 #f.

库: (chezscheme)

(define ht1 (make-weak-eq-hashtable))
(define ht2 (hashtable-copy ht1))
(hashtable-weak? ht2) => #t

过程: (hashtable-ephemeron? obj)

返回: 如果 objephemeron eq hashtableephemeron eqv hashtable, 则为 #t, 否则为 #f.

库: (chezscheme)

(define ht1 (make-ephemeron-eq-hashtable))
(define ht2 (hashtable-copy ht1))
(hashtable-ephemeron? ht2) => #t

过程: (eq-hashtable? obj)

返回: 如果 objeq hashtable, 则为 #t, 否则为 #f.

库: (chezscheme)

(eq-hashtable? (make-eq-hashtable)) => #t
(eq-hashtable? '(not a hash table)) => #f

过程: (eq-hashtable-weak? hashtable)

返回: 如果 hashtable 是弱引用(weak)的,则为 #t, 否则为 #f.

库: (chezscheme)

hashtable 必须是一个 eq hashtable.

(eq-hashtable-weak? (make-eq-hashtable)) => #f
(eq-hashtable-weak? (make-weak-eq-hashtable)) => #t

过程: (eq-hashtable-ephemeron? hashtable)

返回: 如果 hashtable 使用 ephemeron pairs, 则为 #t, 否则为 #f.

库: (chezscheme)

hashtable 必须是 eq hashtable.

(eq-hashtable-ephemeron? (make-eq-hashtable)) => #f
(eq-hashtable-ephemeron? (make-ephemeron-eq-hashtable)) => #t

过程: (eq-hashtable-set! hashtable key value)

返回: 未定义

库: (chezscheme)

hashtable 必须是一个可变的 eq hashtable. keyvalue 可以是任何 scheme 值。

eq-hashtable-set!hashtable 中把值 value 和键 key 关联起来。

(define ht (make-eq-hashtable))
(eq-hashtable-set! ht 'a 73)

过程: (eq-hashtable-ref hashtable key default)

返回: 参见下文

库: (chezscheme)

hashtable 必须是一个 eq hashtable. keydefault 可以是任何 scheme 值。

eq-hashtable-ref 返回 hashtable 中和 key 关联的值。如果在 hashtable 中没有值和 key 相关联,则 eq-hashtable-ref 返回 default.

(define ht (make-eq-hashtable))
(define p1 (cons 'a 'b))
(define p2 (cons 'a 'b))
(eq-hashtable-set! ht p1 73)
(eq-hashtable-ref ht p1 55) => 73
(eq-hashtable-ref ht p2 55) => 55

过程: (eq-hashtable-contains? hashtable key)

返回: 如果在 hashtablekey 有关联值,则为 #t, 否则为 #f.

库: (chezscheme)

hashtable 必须是一个 eq hashtable. key 可以是任何 scheme 值。

(define ht (make-eq-hashtable))
(define p1 (cons 'a 'b))
(define p2 (cons 'a 'b))
(eq-hashtable-set! ht p1 73)
(eq-hashtable-contains? ht p1) => #t
(eq-hashtable-contains? ht p2) => #f

过程: (eq-hashtable-update! hashtable key procedure default)

返回: 未定义

库: (chezscheme)

hashtable 必须是一个可变的 eq hashtable. keydefault 可以是任何 scheme 值。 procedure 应该接受一个参数,返回一个值,并且不应当修改 hashtable.

eq-hashtable-update!procedure 应用于 hashtablekey 关联的值上,如果 key 没有关联值,则应用于 default. 如果 procedure 正常返回,则 eq-hashtable-update!procedure 返回的值关联到 key 上,如果旧的关联存在,则取代旧的关联。

一个不检查所接收参数是否是正确类型的 eq-hashtable-update! 版本可依如下定义。

(define eq-hashtable-update!
  (lambda (ht key proc value)
    (eq-hashtable-set! ht key
                       (proc (eq-hashtable-ref ht key value)))))

一个可以避免多次哈希计算和哈希查找的更高效的 eq-hashtable-update! 实现如下:

(define ht (make-eq-hashtable))
(eq-hashtable-update! ht 'a
                      (lambda (x) (* x 2))
                      55)
(eq-hashtable-ref ht 'a 0) => 110
(eq-hashtable-update! ht 'a
                      (lambda (x) (* x 2))
                      0)
(eq-hashtable-ref ht 'a 0) => 220

过程: (eq-hashtable-cell hashtable key default)

返回: 一个点对(参见下文)

库: (chezscheme)

hashtable 必须是一个 eq hashtable. keydefault 可以是任何 scheme 值。

如果在 hashtable 中没有值与 key 相关联, eq-hashtable-cell 修改 hashtable, 把 default 关联到 key. 它返回一个点对,其 car 是 key, 而 cdr 是 key 关联的值。改变这个点对的 cdr 字段,事实上会更改这个表,使 key 关联到一个新值。 key 是不应该被改动的。

(define ht (make-eq-hashtable))
(define v (vector 'a 'b 'c))
(define cell (eq-hashtable-cell ht v 3))
cell => (#(a b c) . 3)
(eq-hashtable-ref ht v 0) => 3
(set-cdr! cell 4)
(eq-hashtable-ref ht v 0) => 4

过程: (eq-hashtable-delete! hashtable key)

返回: 未定义

库: (chezscheme)

hashtable 必须是一个不可变的 eq hashtable. key 可以是任何 scheme 值。

eq-hashtable-delete! 会删除 keyhashtable 中的任何关联。

(define ht (make-eq-hashtable))
(define p1 (cons 'a 'b))
(define p2 (cons 'a 'b))
(eq-hashtable-set! ht p1 73)
(eq-hashtable-contains? ht p1) => #t
(eq-hashtable-delete! ht p1)
(eq-hashtable-contains? ht p1) => #f
(eq-hashtable-contains? ht p2) => #f
(eq-hashtable-delete! ht p2)

过程: (symbol-hashtable? obj)

返回: 如果 objsymbol hashtable, 则为 #t, 否则为 #f.

库: (chezscheme)

(symbol-hashtable? (make-hashtable symbol-hash eq?)) => #t
(symbol-hashtable? (make-eq-hashtable)) => #f

过程: (symbol-hashtable-set! hashtable key value)

返回: 未定义

库: (chezscheme)

hashtable 必须是一个可变的 symbol hashtable. (symbol hashtable 是通过哈希函数 symbol-hash 和相等性函数 eq?, eqv?, equal?, 或 symbol=? 创建的 hashtable.) key 必须是一个符号, value 可以是任何 scheme 值。

symbol-hashtable-set!hashtable 中的键 key 和值 value 关联起来。

(define ht (make-hashtable symbol-hash eq?))
(symbol-hashtable-ref ht 'a #f) => #f
(symbol-hashtable-set! ht 'a 73)
(symbol-hashtable-ref ht 'a #f) => 73

过程: (symbol-hashtable-ref hashtable key default)

返回: 参见下文

库: (chezscheme)

hashtable 必须是一个 symbol hashtable. (symbol hashtable 是通过哈希函数 symbol-hash 和相等性函数 eq?, eqv?, equal?, 或 symbol=? 创建的 hashtable.) key 必须是一个符号, default 可以是任何 scheme 值。

symbol-hashtable-ref 返回 hashtable 中与 key 关联的值。如果 hashtable 中没有值与 key 相关联,则 symbol-hashtable-ref 返回 default.

(define ht (make-hashtable symbol-hash eq?))
(define k1 'abcd)
(define k2 'not-abcd)
(symbol-hashtable-set! ht k1 "hi")
(symbol-hashtable-ref ht k1 "bye") => "hi"
(symbol-hashtable-ref ht k2 "bye") => "bye"

过程: (symbol-hashtable-contains? hashtable key)

返回: 如果在 hashtablekey 存在关联值,则为 #t, 否则为 #f.

库: (chezscheme)

hashtable 必须是一个 symbol hashtable. (symbol hashtable 是通过哈希函数 symbol-hash 和相等性函数 eq?, eqv?, equal?, 或 symbol=? 创建的 hashtable.) key 必须是一个符号。

(define ht (make-hashtable symbol-hash eq?))
(define k1 'abcd)
(define k2 'not-abcd)
(symbol-hashtable-set! ht k1 "hi")
(symbol-hashtable-contains? ht k1) => #t
(symbol-hashtable-contains? ht k2 ) => #f

过程: (symbol-hashtable-update! hashtable key procedure default)

返回: 未定义

库: (chezscheme)

hashtable 必须是一个可变的 symbol hashtable. (symbol hashtable 是通过哈希函数 symbol-hash 和相等性函数 eq?, eqv?, equal?, 或 symbol=? 创建的 hashtable.) key 必须是一个符号, default 可以是任何 Scheme 值。 procedure 应该接受一个参数,返回一个值,且不应修改 hashtable.

symbol-hashtable-update!procedure 应用于 hashtablekey 关联的值上,如果 key 没有关联值,则应用于 default. 如果 procedure 正常返回,则 symbol-hashtable-update!procedure 返回的值关联到 key 上,如果旧的关联存在,则取代旧的关联。

一个不检查所接收参数是否是正确类型的 symbol-hashtable-update! 版本可依如下定义。

(define symbol-hashtable-update!
  (lambda (ht key proc value)
    (symbol-hashtable-set! ht key
                           (proc (symbol-hashtable-ref ht key value)))))

一个可以避免多次哈希计算和哈希查找的更高效的 symbol-hashtable-update! 实现如下:

(define ht (make-hashtable symbol-hash eq?))
(symbol-hashtable-update! ht 'a
                          (lambda (x) (* x 2))
                          55)
(symbol-hashtable-ref ht 'a 0) => 110
(symbol-hashtable-update! ht 'a
                          (lambda (x) (* x 2))
                          0)
(symbol-hashtable-ref ht 'a 0) => 220

过程: (symbol-hashtable-cell hashtable key default)

返回: 一个点对(参见下文)

库: (chezscheme)

hashtable 必须是一个可变的 symbol hashtable. (symbol hashtable 是通过哈希函数 symbol-hash 和相等性函数 eq?, eqv?, equal?, 或 symbol=? 创建的 hashtable.) key 必须是一个符号, default 可以是任何 Scheme 值。

如果在 hashtable 中没有值与 key 相关联, symbol-hashtable-cell 修改 hashtable, 把 default 关联到 key. 它返回一个点对,其 car 是 key, 而 cdr 是 key 关联的值。改变这个点对的 cdr 字段,事实上会更改这个表,使 key 关联到一个新值。 key 是不应该被改动的。

(define ht (make-hashtable symbol-hash eq?))
(define k 'a-key)
(define cell (symbol-hashtable-cell ht k 3))
cell => (a-key . 3)
(symbol-hashtable-ref ht k 0) => 3
(set-cdr! cell 4)
(symbol-hashtable-ref ht k 0) => 4

过程: (symbol-hashtable-delete! hashtable key)

返回: 未定义

库: (chezscheme)

hashtable 必须是一个可变的 symbol hashtable. (symbol hashtable 是通过哈希函数 symbol-hash 和相等性函数 eq?, eqv?, equal?, 或 symbol=? 创建的 hashtable.) key 必须是一个符号。

symbol-hashtable-delete!hashtable 中删除 key 的任何关联值。

(define ht (make-hashtable symbol-hash eq?))
(define k1 (gensym))
(define k2 (gensym))
(symbol-hashtable-set! ht k1 73)
(symbol-hashtable-contains? ht k1) => #t
(symbol-hashtable-delete! ht k1)
(symbol-hashtable-contains? ht k1) => #f
(symbol-hashtable-contains? ht k2) => #f
(symbol-hashtable-delete! ht k2)

7.13. Record 类型 (Record Types)

Chez Scheme 以一种方式扩展了 R6RS 中的 define-record-type 语法,其通过包含一个以 #f 作为 uid 值的 nongenerative 子句,支持以如下方式显式声明一个 generative record 类型(一种类似双重否定的方式),即,

(nongenerative #f)

这种方式可以和参数对象 require-nongenerative-clause 结合使用,以捕获对 generative record 类型的意外使用,同时避免在必须使用 generative record 类型时的错误误报。 Generative record 类型很少需要被用到,而且通常比较低效,因为每次求值 define-record-clause 时,都要创建一次类型的运行时形式,而不是只在编译(展开)时创建一次。

thread parameter: require-nongenerative-clause

库: (chezscheme)

这个参数对象存有一个布尔值,用来判定 define-record-type 是否必须有一个 nongenerative 子句。默认值是 #f. 上方的引言说明了为什么有人可能会想把它设置为 #t.

7.14. Record 相等性和哈希化 (Record Equality and Hashing)

默认情况下, 基本过程 equal? 使用 eq? 比较 record 实例,即,它区分不是同一个对象 (non-eq?) 的实例,即使它们是同一个类型且有相同的内容。对一个 record 类型(及它没有实现自己专有的相等性比较过程的子类型)的实例,程序可以通过使用 record-type-equal-procedure 来把相等性比较过程和描述这种 record 类型的 record 类型描述符(rtd)关联起来,以重载这种行为。

当比较两个 eq? 的实例时, equal? 总是返回 #t. 当比较两个共享相等性过程 equal-procnon-eq? 的实例时, equal? 使用 equal-proc 比较两个实例。如果两个实例 xy 在继承链的相同位置上继承了一个相等性过程,则它们共享这个相等性过程,即, (record-equal-procedure x y) 返回一个过程 (equal-proc) 而不是 #f. equal? 传递给 equal-proc 3 个参数:2 个实例,及一个能用来递归地比较 2 个实例内的值的 eql? 过程。使用 eql? 进行递归地比较是必要的,这样才能比较潜在的循环结构。当比较两个不共享相等性过程的 non-eq? 实例时, equal? 返回 #f.

通过参数对象 default-record-equal-procedure 可以指定一个用于所有 record 类型(包括不透明类型)的默认相等性过程。只有在实例所属类型没有实现或继承一个类型专用的相等性过程时,这个默认过程才会被使用。

类似的,当 equal-hash 直接哈希化一个 record 实例时,它默认生成一个独立于实例所属类型和内容的值。对于一个 record 类型的实例,通过使用 record-type-hash-procedure 把哈希过程和描述这个 record 类型的 record 类型描述符(rtd)关联起来,程序可以重载这种行为。 record-hash-procedure 过程可以沿着继承链,找到一个特定 record 实例的哈希过程。 equal-hash 传递给哈希过程 2 个参数:实例,和一个可用于递归地哈希化实例中的值的哈希过程。递归地进行哈希化是必要的,这样才能哈希化潜在的循环结构,并使得对共享结构的哈希化更为高效。

通过参数对象 default-record-hash-procedure 可以指定一个用于所有 record 类型(包括不透明类型)的默认哈希过程。只有当实例所属类型没有实现或继承一个类型专用的哈希过程时,这个默认的哈希过程才会被使用。

以下的例子说明了相等性过程和哈希过程的设置。

(define-record-type marble
  (nongenerative)
  (fields color quality))

(record-type-equal-procedure (record-type-descriptor marble)) => #f
(equal? (make-marble 'blue 'medium) (make-marble 'blue 'medium)) => #f
(equal? (make-marble 'blue 'medium) (make-marble 'blue 'high)) => #f

; 当 marbles 的颜色相同时,把它们视作相等
(record-type-equal-procedure (record-type-descriptor marble)
  (lambda (m1 m2 eql?)
    (eql? (marble-color m1) (marble-color m2))))
(record-type-hash-procedure (record-type-descriptor marble)
  (lambda (m hash)
    (hash (marble-color m))))

(equal? (make-marble 'blue 'medium) (make-marble 'blue 'high)) => #t
(equal? (make-marble 'red 'high) (make-marble 'blue 'high)) => #f

(define ht (make-hashtable equal-hash equal?))
(hashtable-set! ht (make-marble 'blue 'medium) "glass")
(hashtable-ref ht (make-marble 'blue 'high) #f) => "glass"

(define-record-type shooter
  (nongenerative)
  (parent marble)
  (fields size))

(equal? (make-marble 'blue 'medium) (make-shooter 'blue 'large 17)) => #t
(equal? (make-shooter 'blue 'large 17) (make-marble 'blue 'medium)) => #t
(hashtable-ref ht (make-shooter 'blue 'high 17) #f) => "glass"

下面的例子说明了相等性过程和哈希过程在循环 record 结构上的应用。

(define-record-type node
  (nongenerative)
  (fields (mutable left) (mutable right)))

(record-type-equal-procedure (record-type-descriptor node)
  (lambda (x y e?)
    (and
      (e? (node-left x) (node-left y))
      (e? (node-right x) (node-right y)))))
(record-type-hash-procedure (record-type-descriptor node)
  (lambda (x hash)
    (+ (hash (node-left x)) (hash (node-right x)) 23)))

(define graph1
  (let ([x (make-node "a" (make-node #f "b"))])
    (node-left-set! (node-right x) x)
    x))
(define graph2
  (let ([x (make-node "a" (make-node (make-node "a" #f) "b"))])
    (node-right-set! (node-left (node-right x)) (node-right x))
    x))
(define graph3
  (let ([x (make-node "a" (make-node #f "c"))])
    (node-left-set! (node-right x) x)
    x))

(equal? graph1 graph2) => #t
(equal? graph1 graph3) => #f
(equal? graph2 graph3) => #f

(define h (make-hashtable equal-hash equal?))
(hashtable-set! h graph1 #t)
(hashtable-ref h graph1 #f) => #t
(hashtable-ref h graph2 #f) => #t
(hashtable-ref h graph3 #f) => #f

过程: (record-type-equal-procedure rtd equal-proc)

返回: 未定义

过程: (record-type-equal-procedure rtd)

返回: 与 rtd 关联的相等性过程,如果不存在,则为 #f

库: (chezscheme)

在第一种形式中, equal-proc 必须是一个过程或 #f. 如果 equal-proc 是一个过程,则 rtdequal-proc 之间会建立一个新的关联,替代任何现存的关联。如果 equal-proc#f, 任何现存的 rtd 和相等性过程之间的关联都会被解除。

第二种形式中, record-type-equal-procedure 返回 rtd 关联的相等性过程,如果不存在,则返回 #f.

当改变一个 record 类型的相等性过程时,如果这个 record 类型有哈希过程,则需要的话也应该更新,对当前相等性过程判定为相等的任意两个实例,要确保生成相同的哈希值。

过程: (record-equal-procedure record1 record2)

返回: record1record2 的共享相等性过程,如果不存在,则为 #f

库: (chezscheme)

record-equal-procedure 遍历两个 record 实例的继承链,尝试寻找每个实例关联有相等性过程的最具体的类型——如果存在的话。如果找到了这样的类型,且对于两个实例来说是一样的类型,则返回其相关联的相等性过程。否则,返回 #f.

过程: (record-type-hash-procedure rtd hash-proc)

返回: 未定义

过程: (record-type-hash-procedure rtd)

返回: rtd 相关联的哈希过程,如果不存在,则为 #f.

库: (chezscheme)

在第一种形式中, hash-proc 必须是一个过程或 #f. 如果 hash-proc 是一个过程,则在 rtdhash-proc 之间建立起新的关联,并替代任何已存在的关联。如果 hash-proc#f, 则删除 rtd 和哈希过程的任何现存关联。

在第二种形式中, record-type-hash-procedure 返回 rtd 关联的哈希过程,如果不存在,则返回 #f.

hash-proc 应该接受两个参数,一个待计算哈希值的实例,和一个哈希过程,用来为这个实例的每个字段计算哈希值,并返回一个非负精确整数。一个 record 类型的哈希过程,应该为此类型的相等性过程判定为相等的任意两个实例生成相同的哈希值。

过程: (record-hash-procedure record)

返回: record 的哈希过程,如果不存在,则为 #f

库: (chezscheme)

record-hash-procedure 遍历此 record 实例的继承链,尝试寻找关联有哈希过程的最具体的类型——如果存在的话。如果找到了这样的类型,则返回其相关联的哈希过程。否则,返回 #f.

thread parameter: default-record-equal-procedure

库: (chezscheme)

如果两个 record 实例都没有类型专用的相等性过程,则由此参数决定它们通过 equal? 进行比较的方式。当此参数的值为 #f (默认值)时, equal? 使用 eq? 来比较这些实例,即,不尝试判定结构上的相等性。否则,此参数的值必须为一个过程,而 equal? 调用此过程来比较实例,此过程传入 3 个实参:2 个实例,和 1 个能用于递归比较实例内任意值的过程。

thread parameter: default-record-hash-procedure

库: (chezscheme)

如果一个 record 实例没有类型专用的哈希过程,则由此参数决定对此实例调用 equal-hash 时使用哪个哈希过程。当此参数的值为 #f (默认值)时, equal-hash 返回一个独立于实例所属类型和内容的值。否则,此参数的值必须为一个过程,而 equal-hash 调用此过程来计算实例的哈希值,此过程传入此 record 实例,和一个用于递归计算实例内任意值的哈希值的过程。此过程应返回一个非负精确整数,并且,对于任意两个默认相等性过程判定为相等的实例,应返回相同的值。

7.15. 传统的 record 类型 (Legacy Record Types)

除了 R6RS 中 record 类型的创建和定义机制——参见“The Scheme Programming Language, 第 4 版”,第 9 章中的讲解, Chez Scheme 仍然支持之前版本中创建新数据类型,或称 record 类型(带有固定数量的命名字段)的机制。本节中介绍的很多过程,只在从 (chezscheme csv7) 库中导入 (import) 之后才可用。

若代码要考虑移植性的话,应该使用 R6RS 机制。

Record 可以通过 define-record 语法形式或通过 make-record-type 过程定义。在 R6RS 中的机制和替代机制中, record 的底层表示和类型描述符是一样的。通过过程机制,一种机制创建的 record 类型可以作为另一种机制创建的 record 类型的父类型,不过通过语法机制则无法做到。

语法接口 (define-record) 是最常用的接口。每个 define-record 语法形式定义一个新 record 类型的构造过程,一个只在对象为此类型时返回 #t 的类型谓词,针对各个字段的访问过程,针对各可变字段的赋值过程。例如,

(define-record point (x y))

创建一个有两个字段,x 和 y 的 point record 类型,同时会定义下列过程:

(make-point x y)     constructor
(point? obj)     predicate
(point-x p)     accessor for field x
(point-y p)     accessor for field y
(set-point-x! p obj)     mutator for field x
(set-point-y! p obj)     mutator for field y

这些过程的命名默认遵循常规的命名惯例,但程序员可以按意愿重载这些默认行为。 define-record 允许程序员控制哪些字段作为生成的构造过程的参数,哪些则由构造过程显式初始化。字段默认是可变的,但可以声明为不可变。字段通常可以包含任意 Scheme 值,但每个字段的内部表示可以被指定,以对其中存储的值的类型施加隐含的约束条件。这些定制选项会在本节后面正式讲述 define-record 时详解。

过程接口 (make-record-type) 可以用于实现必须处理 define-record 语法形式的解释器。对 make-record-type 的每次调用,返回一个表示此 record 类型的 record 类型描述符。 使用此 record 类型描述符,程序可以动态生成构造过程,类型谓词,字段访问过程,及字段修改过程。以下的代码说明了如何使用过程接口创建一个类似的 point record 类型,及相关的定义。

(define point (make-record-type "point" '(x y)))
(define make-point (record-constructor point))
(define point? (record-predicate point))
(define point-x (record-field-accessor point 'x))
(define point-y (record-field-accessor point 'y))
(define set-point-x! (record-field-mutator point 'x))
(define set-point-y! (record-field-mutator point 'y))

过程接口比语法接口更灵活,但这种灵活性会导致程序的可读性较差,也会降低编译器生成高效代码的能力。只要语法接口能完成任务,程序员应该尽可能使用它。

record 类型描述符也可以从 record 类型的实例中提取,无论这个类型是通过 define-record 还是通过 make-record-type 生成的,而且提取出的描述符也可用于生成构造过程,谓词,访问器,和修改器,不过有如下关于 record-type-descriptor 的描述中所注明的一些局限。这是个强大的特性,使得编写可移植的打印器和对象检查器成为可能。例如,在默认 record 打印器中使用此特性的打印器,以及使用它的检查器,以支持在调试期间检查和修改系统或用户定义的 record.

一个父 record 类型可以在 define-record 语法中指明,或作为 make-record-type 的一个可选参数。一个新的 record 继承父 record 的字段,而且每个新 record 类型的实例也被认为是父类型的实例,所以父类型的访问器和修改器也可用于新类型的字段。

Record 类型定义可以被分为 generative 或 nongenerative. 每个 generative record 定义生成一个新的类型,而一个给定的 nongenerative record 定义无论出现多少次,也只会生成一个类型。由于 record 的访问器和设置器只适用于同类型的对象,所以这个区别在语义上是非常重要的。

语法接口 (define-record) 的 record 定义,默认是在展开期生成的,即,一个新的 record 类型是在代码展开期创建的。当交互输入,从源码载入,或用编译文件编译时,对每个语法形式,展开在编译或解释之前进行一次。因此,对单个 define-record 语法形式的多次求值,例如,在一个被调用多次的过程体中,也总是生成相同的 record 类型。

不在同一处的 define-record 语法形式通常生成不同的类型,即使其形式在文本上是一样的。唯一的例外是当 record 的名字被指定为一个生成的符号时,即 gensym (参见 152 页)。一个名字由 gensym 指定的 record 定义,其多份拷贝也总是生成同一个 record 类型,即,这种定义是 nongenerative 的。 record 定义的每份拷贝必须包含相同顺序的同样字段和字段修饰符;当拥有同样生成名字的两个不同的 record 类型被加载进同一个 Scheme 进程时,一个条件类型的 &assertion 异常会被抛出。

过程接口 (make-record-type) 的 record 定义默认是运行时生成的。即,对 make-record-type 的每个调用通常都会生成一个新的 record 类型。和语法接口一样,唯一的例外是当类型名是以 gensym 方式指定时,这种情况下, record 类型是完全 nongenerative 的。

一个 record 类型,默认以如下方式打印:

#[type-name field ...]

field ...record 中字段内容的打印形式, type-name 是一个生成的符号,即 gensym (参见 152 页),唯一标识了此 record 类型。对于 nongenerative record, type-name 是程序提供的 gensym. 否则,它是一个由 define-recordmake-record-type 给出美观名字(参见 152 页)的 gensym.

The default printing of records of a given type may be overridden with record-writer. 一个给定类型的 record 的默认打印形式,可以通过 record-writer 重载。

默认的 record 语法也能用作读取器的输入,只要在读取器所在的 Scheme 会话中,对应的 record 类型已经被定义。参数对象 record-reader 可以被用于指定一个替代生成的名字的,供读取器辨识的不同名字。以这种方式指定一个不同的名字,同时会改变 record 打印出的名字。读取器若遇到 #!r6rs ,则会在其后的输入流中禁用这些扩展,除非在更近的位置遇到 #!chezscheme.

标记 (#n=) 和引用 (#n#) 语法可在 record 语法内使用,用于根据意图创建共享或环状结构。不过,所有环必须是可解析的,不能改变一个不可变 record 字段。即,任何环必须包含至少一个指向可变字段的指针,不论它是个可变的 record 字段,还是一个内置对象类型(如点对或向量)的可变字段。

当参数过程 print-record 被设置为 #f 时, record 使用更简单的语法打印

#<record of type name>

其中 namerecord 的美观名字(不是完整的 gensym ),或是首次分配给这个 record 类型的读取器名字。

语法: (define-record name (fld1 …) ((fld2 init) …) (opt …))

语法: (define-record name parent (fld1 …) ((fld2 init) …) (opt …))

返回: 未定义

库: (chezscheme)

define-record 语法形式是一种定义,可以出现在,也只能出现在一切其它定义可以出现的地方。

define-record 创建一个新的 record 类型,包含一系列指定的具名字段,同时定义一系列过程,用于创建和操控此类型的实例。

name 必须是一个标识符。如果 name 是一个生成的符号 (gensym), 则 record 定义是 nongenerative 的,否则,它是展开期 generative 的。(参见本节前部关于 generativity 的讨论)。

每个 fld 必须是一个字段名标识符,或必须采用以下语法形式

(class type field-name)

classtype 是可选的,而 field-name 是一个标识符。如果提供 class, 则必须指定不可变关键字或可变关键字。如果指定不可变 class 指示符,则字段是不可变的;否则,字段是可变的。 type, 如果存在的话,则指定字段如何表示,如下所示。

ptr     any Scheme object
scheme-object     same as ptr
int     a C int
unsigned     a C unsigned int
short     a C short
unsigned-short     a C unsigned short
long     a C long
unsigned-long     a C unsigned long
iptr     a signed integer the size of a ptr
uptr     an unsigned integer the size of a ptr
float     a C float
double     a C double
integer-8     an eight-bit signed integer
unsigned-8     an eight-bit unsigned integer
integer-16     a 16-bit signed integer
unsigned-16     a 16-bit unsigned integer
integer-32     a 32-bit signed integer
unsigned-32     a 32-bit unsigned integer
integer-64     a 64-bit signed integer
unsigned-64     a 64-bit unsigned integer
single-float     a 32-bit single floating point number
double-float     a 64-bit double floating point number

如果指定一个类型,则字段只能包含指定类型的对象。如果没有指定类型,则字段为 ptr 类型,即,它可以包含任意 Scheme 对象。

字段标识符命名 record 的字段名。通过 fld1 ... 描述的 n 个字段的值,由传递给生成的构造过程的 n 个参数指定。剩余字段的值── fld2 ..., 由对应的表达式── init ... 给出。每个 init 被求值时所处的作用域,为 fld1 ... 给出的字段名和 fld2 ... 中的字段名组成的集合中位于 init 之前的部分,就像在一个 let* 表达式中一样。这些字段名在初始化期间绑定到对应字段的值上。

如果指定了 parent ,则 parent 名字所指代的类型为 record 的父类型。新的类型继承父类型的所有字段,并且新类型的实例同时也被判定为父类型。如果未指定 parent, 则父类型为一个没有字段的 record 基类。

define-record 定义了下列过程:

  • 一个名为 make-name 的构造过程

  • 一个名为 name? 的类型谓词

  • 对每个非继承来的字段生成一个名为 name-fieldname 的访问过程

  • 对每个非继承来的可变字段生成一个名为 set-name-fieldname! 的赋值过程

如果没有指定父类型,则构造过程的行为类似如下定义

(define make-name
  (lambda (id1 ...)
    (let* ([id2 init] ...)
      body)))

其中 id1 ... 是由 fld1 ...定义的字段名, id2 ... 是由 fld2 ...定义的字段名,而 body 通过标识符 id1 ...id2 ... 的值构造 record.

如果指定了父类型,则父类型的参数排在前面,同时,在 record 中,父类型的字段也插入到子类型的字段前面。

选项 opt ... 控制生成的构造过程,谓词,访问器和修改器的命名选择。

(constructor id)
(predicate id)
(prefix string)

选项 (constructor id) 使得生成的构造过程的名字是 id 而非 make-name. 类似,选项 (predicate id) 使得生成的谓词名是 id 而非 name?. 选项 (prefix string) 决定在生成的访问器和修改器名字中,用什么前缀取代 name-.

如果不需要选项,则第三个子表达式, (opt ...), 可以省略。如果除了通过构造过程的参数初始化的字段外,其它字段和选项都不需要的话,第二个和第三个子表达式都能省略。如果指定选项,则第二个子表达式必须存在,即使其不包含任何字段声明。

这里是一个不包含继承和选项的简单例子。

(define-record marble (color quality))
(define x (make-marble 'blue 'medium))
(marble? x) => #t
(pair? x) => #f
(vector? x) => #f
(marble-color x) => blue
(marble-quality x) => medium
(set-marble-quality! x 'low)
(marble-quality x) => low

(define-record marble ((immutable color) (mutable quality))
  (((mutable shape) (if (eq? quality 'high) 'round 'unknown))))
(marble-shape (make-marble 'blue 'high)) => round
(marble-shape (make-marble 'blue 'low)) => unknown
(define x (make-marble 'blue 'high))
(set-marble-quality! x 'low)
(marble-shape x) => round
(set-marble-shape! x 'half-round)
(marble-shape x) => half-round

以下是一个说明继承的例子。

(define-record shape (x y))
(define-record point shape ())
(define-record circle shape (radius))

(define a (make-point 7 -3))
(shape? a) => #t
(point? a) => #t
(circle? a) => #f

(shape-x a) => 7
(set-shape-y! a (- (shape-y a) 1))
(shape-y a) => -4

(define b (make-circle 7 -3 1))
(shape? b) => #t
(point? b) => #f
(circle? b) => #t

(circle-radius b) => 1
(circle-radius a) => exception: not of type circle

(define c (make-shape 0 0))
(shape? c) => #t
(point? c) => #f
(circle? c) => #f

这个例子示范和选项的使用:

(define-record pair (car cdr)
  ()
  ((constructor cons)
   (prefix "")))

(define x (cons 'a 'b))
(car x) => a
(cdr x) => b
(pair? x) => #t

(pair? '(a b c)) => #f
x => #[#{pair bdhavk1bwafxyss1-a} a b]

这个例子说明了指定读取器命名,不可变字段,以及图标记和引用语法的用法。

(define-record triple ((immutable x1) (mutable x2) (immutable x3)))
(record-reader 'triple (type-descriptor triple))

(let ([t '#[triple #1=(1 2) (3 4) #1#]])
  (eq? (triple-x1 t) (triple-x3 t))) => #t
(let ([x '(#1=(1 2) . #[triple #1# b c])])
  (eq? (car x) (triple-x1 (cdr x)))) => #t
(let ([t #[triple #1# (3 4) #1=(1 2)]])
  (eq? (triple-x1 t) (triple-x3 t))) => #t
(let ([t '#1=#[triple a #1# c]])
  (eq? t (triple-x2 t))) => #t
(let ([t '#1=(#[triple #1# b #1#])])
  (and (eq? t (triple-x1 (car t)))
       (eq? t (triple-x1 (car t))))) => #t

只有当一个可变 record 字段或某个其它对象的可变位置包含在环中时,通过标记和引用语法建立的环才能被解析,如同前两个例子中所表现的。如果只包含不可变字段,则一个条件类型的 &lexical 异常会被抛出。

'#1=#[triple #1# (3 4) #1#] => exception

以下例子说明了 nongenerative record 定义的用法。

(module A (point-disp)
        (define-record #{point bdhavk1bwafxyss1-b} (x y))
        (define square (lambda (x) (* x x)))
        (define point-disp
          (lambda (p1 p2)
            (sqrt (+ (square (- (point-x p1) (point-x p2)))
                     (square (- (point-y p1) (point-y p2))))))))

(module B (base-disp)
        (define-record #{point bdhavk1bwafxyss1-b} (x y))
        (import A)
        (define base-disp
          (lambda (p)
            (point-disp (make-point 0 0) p))))

(let ()
  (import B)
  (define-record #{point bdhavk1bwafxyss1-b} (x y))
  (base-disp (make-point 3 4))) => 5

即使程序的不同组件是从不同的源文件加载的,或从不同目标文件加载且是分别编译的,这个用法也成立。

语法: predicate

语法: prefix

语法: constructor

库: (chezscheme)

这些标识符是 define-record 的辅助关键字。除了在被识别为辅助关键字的上下文中,在其它任何地方引用这些标识符都是违反语法的。 mutableimmutable 也是 define-record 的辅助关键字,和 R6RS 中的 define-record-type 一样。

语法: (type-descriptor name)

返回: 与 name 相关联的 record 类型描述符

库: (chezscheme)

name 必须命名一个由 define-recorddefine-record-type 定义的 record 类型。

这个语法形式等价于 R6RS 中 record-type-descriptor 的语法形式。

record 类型描述符在使用 record-readerrecord-writer 重载默认读写语法时很有用,也可和本节后面将要讲述的过程接口 routines 配合使用。

(define-record frob ())
(type-descriptor frob) => #<record type frob>

过程: (record-reader name)

返回: name 关联的 record 类型描述符

过程: (record-reader rtd)

返回: rtd 关联的第一个名字

过程: (record-reader name rtd)

返回: 未定义

过程: (record-reader name #f)

返回: 未定义

过程: (record-reader rtd #f)

返回: 未定义

库: (chezscheme)

name 必须是一个符号, rtd 必须是一个 record 类型描述符。

传入一个参数时, record-reader 用于获取 name 关联的 record 类型,或 record 类型关联的名字。如果没建立过关联,则 record-reader 返回 #f.

传入参数 namertd 时, record-readerrtd 注册为 record 类型描述符,用于在读取过程遇到一个用 name 命名的 record,并用默认 record 语法打印时。

传入参数 name#f 时, record-reader 移除 name 和 record 类型描述符之间的任何关联。类似的,传入参数 rtd#f 时, record-reader 移除 rtd 和一个名字间的任何关联。

(define-record marble (color quality))
(define m (make-marble 'blue 'perfect))
m => #[#{marble bdhavk1bwafxyss1-c} blue perfect]

(record-reader (type-descriptor marble)) => #f
(record-reader 'marble) => #f

(record-reader 'marble (type-descriptor marble))
(marble-color '#[marble red miserable]) => red

(record-reader (type-descriptor marble)) => marble
(record-reader 'marble) => #<record type marble>

(record-reader (type-descriptor marble) #f)
(record-reader (type-descriptor marble)) => #f
(record-reader 'marble) => #f

(record-reader 'marble (type-descriptor marble))
(record-reader 'marble #f)
(record-reader (type-descriptor marble)) => #f
(record-reader 'marble) => #f

引入 record 读取器也会改变 record 的默认打印方式。打印器总是选择最早分配给 record 的读取器名字——如果存在的话,取代 record 的唯一名字,就像上面这个连贯的例子所阐释的。

(record-reader 'marble (type-descriptor marble))
(make-marble 'pink 'splendid) => #[marble pink splendid]

过程: (record-writer rtd)

返回: rtd 关联的 record writer

过程: (record-writer rtd procedure)

返回: 未定义

库: (chezscheme)

rtd 必须是一个 record 类型描述符,且 procedure 应该接受 3 个参数,如下所述。

当只传入一个参数时,record-writer 返回 rtd 关联的 record writer, 对所有 record 来说,最初都是默认 record writer. 默认打印方法以统一的语法打印所有的 record,包括生成的 record 名字,各字段的值,一如在本节的介绍部分描述的。

当传入两个参数时, record-writerrtdprocedure 之间建立一个新的关联,从而使过程被用于打印器,以代替给定类型的 record 的默认打印器。打印器传给 procedure 三个参数:record r, 端口 p, 和一个应该被用于输出 record 打印方法选择包含在其打印形式中的任意 Scheme 对象的值的过程 wr, 例如,record 字段的值。

(define-record marble (color quality))
(define m (make-marble 'blue 'medium))

m => #[#{marble bdhavk1bwafxyss1-d} blue medium]

(record-writer (type-descriptor marble)
               (lambda (r p wr)
                 (display "#<" p)
                 (wr (marble-quality r) p)
                 (display " quality " p)
                 (wr (marble-color r) p)
                 (display " marble>" p)))

m => #<medium quality blue marble>

只有当 print-record#t (默认值)时, record writer 才被使用。当参数 print-record 被设为 #f 时,record 使用一种只指明 record 类型的精简语法打印。

(parameterize ([print-record #f])
  (format "~s" m)) => "#<record of type marble>"

在打印一个单独的 record 期间,一个打印方法可能被调用不只一次,以支持环检测和图打印(参见 print-graph),所以,不建议使用除了打印到指定端口外,还产生副作用的打印方法。在打印一个单独的 record 期间,只要一个打印方法被调用不只一次,则在除一次调用以外的所有调用中,会使用一个通用的 "bit sink" 端口来自动阻止输出,从而,只有对象的一份拷贝会出现在实际的端口中。为避免混乱环检测和图打印算法,对于每个对象,打印方法应该总是产生相同的打印形式。此外,打印方法通常应该使用传入的过程 wr 打印子对象,尽管对于原子型的值,如字符串或数字,可能通过直接调用 display 或 write 或其它方法来打印。

(let ()
  (define-record ref () ((contents 'nothing)))
  (record-writer (type-descriptor ref)
                 (lambda (r p wr)
                   (display "<" p)
                   (wr (ref-contents r) p)
                   (display ">" p)))
  (let ([ref-lexive (make-ref)])
    (set-ref-contents! ref-lexive ref-lexive)
    ref-lexive)) => #0=<#0#>

打印方法不需要关注如何处理参数 print-level 值为非假的情况。打印器会自动处理 print-level ,即使使用的是用户自定义的打印过程。由于 record 一般只包含少量,固定数目的字段,所以通常也可以忽略 print-length 的非假值。

(print-level 3)
(let ()
  (define-record ref () ((contents 'nothing)))
  (record-writer (type-descriptor ref)
                 (lambda (r p wr)
                   (display "<" p)
                   (wr (ref-contents r) p)
                   (display ">" p)))
  (let ([ref-lexive (make-ref)])
    (set-ref-contents! ref-lexive ref-lexive)
    ref-lexive)) => <<<<#[...]>>>>

thread parameter: print-record

库: (chezscheme)

这个参数控制 record 的打印形式。如果设为真(默认值),则使用与 record 类型相关联的 record writer 打印此类型 record。如果设为假,所有 record 都以语法 #<record of type name> 打印,其中 name 是 record 的类型名,即 record-type-name 的返回值。

过程: (make-record-type type-name fields)

过程: (make-record-type parent-rtd type-name fields)

返回: 一个新 record 类型的类型描述符

库: (chezscheme)

make-record-type 创建一个新的数据类型,并返回一个 record 类型描述符,一个表示这个新类型的值。这个新类型和其它所有类型都是互斥的。

如果被传入, parent-rtd 必须是一个 record 类型描述符。

type-name 必须是一个字符串或 gensym. 如果 type-name 是一个字符串,则生成一个新的 record 类型。如果 type-name 是一个 gensym, 只有在没有具有相同 gensym 的类型被定义过时,才会生成一个新的 record 类型。如果已经定义过一个,则父类型和字段必须与现存类型中的一致,同时,现存的 record 类型会被使用。如果父类型和字段与现存的不一致,则一个条件类型的 &assertion 异常会被抛出。

fields 必须是一个字段描述符的列表,其中每一个描述了一个新类型的实例的字段。一个字段描述符是一个符号或一个以下形式的列表:

(class type field-name)

其中 classtype 是可选的。 field-name 必须是一个符号。如果指定 class ,则必须是符号 immutable 或符号 mutable 。如果提供 immutable 类别指示符,则字段是不可变的;否则,字段是可变的。 type, 如果提供的话,指定了字段如何表示。类型类型和 174 页上对 define-record 的描述中给出的一样。

如果指定了类型,则字段只能包含指定类型的对象。如果没有指定类型,字段则是 ptr 类型,意思是它可以包含任意 Scheme 对象。

修改字符串 type-namefields 列表或它的任何子列表的程序行为是未定义的。

record 类型描述符可以作为参数传递给 R6RS 中的下列任何过程

  • record-constructor,
  • record-predicate,
  • record-accessor, and
  • record-mutator,

Chez Scheme 中的变体

  • record-constructor,
  • record-field-accessor, and
  • record-field-mutator

以获取创建和操控新类型 record 的过程。

(define marble
  (make-record-type "marble"
                    '(color quality)
                    (lambda (r p wr)
                      (display "#<" p)
                      (wr (marble-quality r) p)
                      (display " quality " p)
                      (wr (marble-color r) p)
                      (display " marble>" p))))
(define make-marble
  (record-constructor marble))
(define marble?
  (record-predicate marble))
(define marble-color
  (record-field-accessor marble 'color))
(define marble-quality
  (record-field-accessor marble 'quality))
(define set-marble-quality!
  (record-field-mutator marble 'quality))
(define x (make-marble 'blue 'high))
(marble? x) => #t
(marble-quality x) => high
(set-marble-quality! x 'low)
(marble-quality x) => low
x => #<low quality blue marble>

字段在 fields 中出现的顺序是十分重要的。虽然通常字段名是互不相同的,但也有可能一个字段名与字段列表中的另一个字段名相同,或与继承来的字段名相同。在这种情况下,调用 record-field-accessorrecord-field-mutator 时必须使用字段序数选择字段。序数的范围是从零到字段数量减一。如果有父字段的话,则父字段排在前面,后面按给定的次序跟着 fields 中的字段。

(define r1 (make-record-type "r1" '(t t)))
(define r2 (make-record-type r1 "r2" '(t)))
(define r3 (make-record-type r2 "r3" '(t t t)))

(define x ((record-constructor r3) 'a 'b 'c 'd 'e 'f))
((record-field-accessor r3 0) x) => a
((record-field-accessor r3 2) x) => c
((record-field-accessor r3 4) x) => e
((record-field-accessor r3 't) x) => 未定义

过程: (record-constructor rcd)

过程: (record-constructor rtd)

返回: rtd 所表示的 record 类型的构造过程

库: (chezscheme)

和这个过程的 R6RS 中的版本一样,这个过程可以传入一个 record 构造描述符—— rcd, 其决定构造过程的行为。它也可以传入一个 record 类型描述符, rtd ,这种情况下,构造过程接受的参数数量和 record 的字段数量一样;这些参数是字段的初始值,以 record 类型描述符创建时给定的顺序排序。

过程: (record-field-accessor rtd field-id)

返回: 指定字段的访问器

库: (chezscheme csv7)

rtd 必须是 record 类型描述符, field-id 必须是符号或是字段序数,即,一个非负的精确整数,小于给定 record 类型的字段数。指定的字段必须是可访问的。

生成的访问器接受一个参数,其必须是一个 rtd 所表示类型的 record. 它返回此 record 指定字段的内容。

过程: (record-field-accessible? rtd field-id)

返回: 如果指定的字段可访问,则为 #t, 否则为 #f.

库: (chezscheme csv7)

rtd 必须是 record 类型描述符, field-id 必须是符号或是字段序数,即,一个非负的精确整数,小于给定 record 类型的字段数。

如果编译器可以确认 record 的一个字段没有被访问,则它可以自由地排除这个字段。在做出这个决定时,编辑器可以自由地忽略以下可能性,即访问器可能是通过一个 record 类型描述符创建的,而这个描述符是通过在此 record 类型的一个实例上调用 record-type-descripter 而获得的。

过程: (record-field-mutator rtd field-id)

返回: 指定字段的修改器

库: (chezscheme csv7)

rtd 必须是 record 类型描述符, field-id 必须是符号或是字段序数,即,一个非负的精确整数,小于给定 record 类型的字段数。指定字段必须是可变的。

修改器接受两个参数, robj. r 必须是 rtd 表示的类型的 record. obj 必须是与 record 类型描述符创建时所声明的指定字段的类型相兼容的值。 obj 存储在 record 的指定字段中。

过程: (record-field-mutable? rtd field-id)

返回: 如果指定的字段是可变的,则为 #t, 否则为 #f.

库: (chezscheme csv7)

rtd 必须是 record 类型描述符, field-id 必须是符号或者字段序数,即,一个非负的精确整数,小于给定 record 类型的字段数量。

任何声明为不可变的字段为不可变的。此外,编译器可以自由地把一个字段看作不可变的,只要它可以确证这个字段从来没有被赋值。在做出这个决定时,编辑器可以自由地忽略以下可能性,即修改器可能是通过一个 record 类型描述符创建的,而这个描述符是通过在此 record 类型的一个实例上调用 record-type-descripter 而获得的。

过程: (record-type-name rtd)

返回: 由 rtd 表示的 record 类型的名字

库: (chezscheme csv7)

rtd 必须是一个 record 类型描述符。

名字总是一个字符串。如果在 define-record 语法形式或 make-record-type 调用中,一个 gensym 被作为 record 类型名提供,则结果为 gensym 的美观名字(参见 7.9 节)。

(record-type-name (make-record-type "empty" '())) => "empty"

(define-record #{point bdhavk1bwafxyss1-b} (x y))
(define p (type-descriptor #{point bdhavk1bwafxyss1-b}))
(record-type-name p) => "point"

过程: (record-type-symbol rtd)

返回: 与 rtd 关联的生成的符号

库: (chezscheme csv7)

rtd 必须是一个 record 类型描述符。

(define e (make-record-type "empty" '()))
(record-type-symbol e) => #{empty bdhavk1bwafxyss1-e}

(define-record #{point bdhavk1bwafxyss1-b} (x y))
(define p (type-descriptor #{point bdhavk1bwafxyss1-b}))
(record-type-symbol p) => #{point bdhavk1bwafxyss1-b}

过程: (record-type-field-names rtd)

返回: rtd 所表示的类型的字段名列表

库: (chezscheme csv7)

rtd 必须是一个 record 类型描述符。字段名是符号。

(define-record triple ((immutable x1) (mutable x2) (immutable x3)))
(record-type-field-names (type-descriptor triple)) => (x1 x2 x3)

过程: (record-type-field-decls rtd)

返回: rtd 所表示的类型的字段声明列表

库: (chezscheme csv7)

rtd 必须是一个 record 类型描述符。每个字段声明有以下语法形式:

(class type field-name)

其中 class, type, 和 field-name 一如 make-record-type 后面的描述。

(define-record shape (x y))
(define-record circle shape (radius))

(record-type-field-decls
 (type-descriptor circle)) => ((mutable ptr x)
                               (mutable ptr y)
                               (mutable ptr radius))

过程: (record? obj)

返回: 如果 obj 是 record,则为 #t, 否则为 #f.

过程: (record? obj rtd)

返回: 如果 obj 是指定类型的 record,则为 #t, 否则为 #f.

库: (chezscheme)

rtd (如果存在) 必须是一个 record 类型描述符。

一个 record 是“指定的类型”,意指它是指定 record 类型或其某个父类型的实例。通过 record-predicate 为一个 record 类型描述符 rtd 生成的谓词等价于如下定义。

(lambda (x) (record? x rtd))

过程: (record-type-descriptor rec)

返回: rec 的 record 类型描述符

库: (chezscheme csv7)

rec 必须是一个 record. 这个过程是用在可移植的打印器和调试器的定义中。对于用 make-record-type 创建的 record, 它可能和 make-record-type 返回的描述符不一样。可参见 record-field-accessible? 后面和 record-field-mutable? 前面,关于字段可访问性和可变性的注解。

这个过程等价于 R6RS 中的 record-rtd 过程。

(define rtd (make-record-type "frob" '(blit blat)))
rtd => #<record type frob>
(define x ((record-constructor rtd) 1 2))
(record-type-descriptor x) => #<record type frob>
(eq? (record-type-descriptor x) rtd) => 未定义

7.16. 过程 (Procedures)

过程: (procedure-arity-mask proc)

返回: 一个精确的整数位掩码,指明了 proc 可接受的参数数量

库: (chezscheme)

位掩码以补码的形式表示,当且仅当 proc 接受 n 个参数时,每个索引 n 处的位被设定。

补码编码意味着,如果 proc 接受 n 个或更多的参数,则编码是一个负数,由于所有 n 及更高的位均被设定。例如,如果 proc 接受任意数量的参数,则所有位设置的补码编码为-1.

(procedure-arity-mask (lambda () 'none)) => 1
(procedure-arity-mask car) => 2
(procedure-arity-mask (case-lambda [() 'none] [(x) x])) => 3
(procedure-arity-mask (lambda x x)) => -1
(procedure-arity-mask (case-lambda [() 'none] [(x y . z) x])) => -3
(procedure-arity-mask (case-lambda)) => 0
(logbit? 1 (procedure-arity-mask pair?)) => #t
(logbit? 2 (procedure-arity-mask pair?)) => #f
(logbit? 2 (procedure-arity-mask cons)) => #t

Chez Scheme Version 9 User's Guide Copyright © 2018 Cisco Systems, Inc. Licensed under the Apache License Version 2.0 (full copyright notice.). Revised October 2018 for Chez Scheme Version 9.5.1 about this book

results matching ""

    No results matching ""