7. 对象操作
本章介绍了 Chez Scheme 中针对非数值对象的特定操作,包括 pairs, numbers 等标准对象,以及 boxes, records 等 Chez Scheme 扩展对象。第 8 章介绍了针对数字的操作。关于对象的标准操作的更多描述,参见“The Scheme Programming Language,第 4 版”的第 6 章,或 R6RS.
7.1. R6RS 中缺少的类型谓词
过程: (enum-set? obj)
返回: 如果 obj
为 enum set
则为 #t
, 否则为 #f
.
库: (chezscheme)
此一谓词应当被定义,但缺失于 R6RS 中。
过程: (record-constructor-descriptor? obj)
返回: 如果 obj
为 record 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 相等的对象。
对于 substq
和 substq!
,相等性测试是基于 eq?
, substv
和 substv!
是基于 eqv?
, 而 subst
和 subst!
是基于 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)
返回: char1
和 char2
间的整数差值
库: (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)
src
和 dst
必须是字符串,且 dst
必须可变。 src-start
, dst-start
, 以及 n
必须是精确的非负整数。 src-start
和 n
的和绝对不能超过 src
的长度, 而 dst-start
和 n
的和则一定不能超过 dst
的长度。
string-copy!
以 src
中起始于 src-start
,长度为 n
字节的部分,覆盖 dst
中起始于 dst-start
,长度为 n
字节的部分。即使 dst
和 src
是同一个字符串,且源和目标位置相互重叠,这个操作也能生效。即,在操作开始时,目标位置先被源字符串中的字符填充。
(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
必须是可变的。 string
于 start
(包含) 和 end
(不包含) 之间的字符均被设置为 char
. start
和 end
必须是非负整数; 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, 类似于向量,但只包含 fixnum 。 fxvector 的输出形式以前缀 #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)
返回: 一个长度为 n
的 fxvector
库: (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)
返回: 如果 obj
是 box, 则为 #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-prefix
和 gensym-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->string
或 gensym->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-prefix
和 gensym-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)
返回: 如果 obj
是 gensym ,则为 #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 给每个符号关联一个属性列表,允许多个键值对直接和符号存储在一起。使用过程 putprop
和 getprop
, 可以把新的键值对存入属性列表,或以类似关联列表的使用方式获取值。属性列表通常用来存储与符号自身相关的信息。比如,一个自然语言程序,可以使用符号来表示单词,利用它们的属性列表来存储有关用法和词意的信息。
putprop
在符号的属性列表中把键值关联起来。虽然键通常是符号,但键和值实际可以是任意类型的对象。
putprop
可以用来创建新属性,或改变已有属性。
参见 getprop
后面的例子。
过程: (getprop symbol key)
过程: (getprop symbol key default)
返回: symbol
属性列表中与 key
关联的值
库: (chezscheme)
getprop
在 symbol
的属性列表中搜索,寻找与 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
对应的属性存在,则 remprop
从 symbol
的属性列表中移除此属性。
(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
指定的顺序融合 list1
和 list2
.
库: (chezscheme)
predicate
应该是一个接收两个参数的过程,当它的第一个参数在融合后的列表中要排在第二个参数之前时返回 #t
. 它不应有任何负作用。即,如果把 predicate
作用于两个对象 x 和 y, x 来自于第一个列表,而 y 来自于第二个列表,它应该只在 x 应在输出列表中排在 y 之前时返回 #t
. 如果满足这个限制条件, merge
和 merge!
就是可靠的,其中来自 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 Scheme 对 hashtable 机制进行了多方面的扩展,包括一种直接访问 hashtable 中键值对的方法,对 weak(弱引用) eq hashtable 和 weak eqv hashtable 的支持,及一些为 eq hashtable 和 symbol hashtable 定制的过程。
过程: (hashtable-cell hashtable key default)
返回: 一个点对 (参见下文)
库: (chezscheme)
hashtable
必须是一个 hashtable. key
和 default
可以是任意 Scheme 值。
如果 hashtable
中没有值与 key
相关联, hashtable-cell
修改 hashtable
以使 key
关联到 default
. 它返回一个点对, car 是 key
, 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-hashtable
和 make-eqv-hashtable
是类似的,只是 hashtable 中的键是弱引用的,即,面对垃圾回收,它们并不受到保护。被垃圾收集器回收的键会从表中移除,而它们的关联值最晚会在下次表被修改时丢弃。
只要键没有被回收, hashtable 中的值就能被正常引用,因为键和值是使用弱引用点对(weak pair)配对在一起的。因此,如果 hashtable 中的一个值反向指回它自己的键,就会阻止垃圾收集器回收这个键。参见 make-ephemeron-eq-hashtable
和 make-ephemeron-eqv-hashtable
.
通过 hashtable-copy
复制一个 weak eq hashtable 或 weak eqv hashtable 产生的拷贝也是弱引用的。如果此拷贝是不可变的,不可访问的键仍然可以从表中被丢弃,即使表中的内容在其它情况下是不变的。这种效果可以通过 hashtable-keys
和 hashtable-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-hashtable
和 make-weak-eqv-hashtable
, 但表中的值可以在(直接或间接)指向表中的键时,却不阻止垃圾收集器回收此键,因为键与值是通过 ephemeron pairs 配对的。
通过 hashtable-copy
复制一个 ephemeron eq hashtable 或 ephemeron eqv hashtable 产生的拷贝也是 ephemeron table. 一个不可访问的键从一个不可变的 ephemeron hashtable 中被丢弃的方式,和在不可变的 weak hashtable 中相同。
(define ht1 (make-ephemeron-eq-hashtable))
(define ht2 (make-ephemeron-eq-hashtable 32))
过程: (hashtable-weak? obj)
返回: 如果 obj
是 weak eq hashtable 或 weak eqv hashtable, 则为 #t
, 否则为 #f
.
库: (chezscheme)
(define ht1 (make-weak-eq-hashtable))
(define ht2 (hashtable-copy ht1))
(hashtable-weak? ht2) => #t
过程: (hashtable-ephemeron? obj)
返回: 如果 obj
是 ephemeron eq hashtable 或 ephemeron eqv hashtable, 则为 #t
, 否则为 #f
.
库: (chezscheme)
(define ht1 (make-ephemeron-eq-hashtable))
(define ht2 (hashtable-copy ht1))
(hashtable-ephemeron? ht2) => #t
过程: (eq-hashtable? obj)
返回: 如果 obj
是 eq 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. key
和 value
可以是任何 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. key
和 default
可以是任何 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)
返回: 如果在 hashtable
中 key
有关联值,则为 #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. key
和 default
可以是任何 scheme 值。 procedure
应该接受一个参数,返回一个值,并且不应当修改 hashtable
.
eq-hashtable-update!
把 procedure
应用于 hashtable
中 key
关联的值上,如果 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. key
和 default
可以是任何 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!
会删除 key
在 hashtable
中的任何关联。
(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)
返回: 如果 obj
是 symbol 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)
返回: 如果在 hashtable
中 key
存在关联值,则为 #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
应用于 hashtable
中 key
关联的值上,如果 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-proc 的 non-eq? 的实例时, equal?
使用 equal-proc 比较两个实例。如果两个实例 x 和 y 在继承链的相同位置上继承了一个相等性过程,则它们共享这个相等性过程,即, (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
是一个过程,则 rtd
和 equal-proc
之间会建立一个新的关联,替代任何现存的关联。如果 equal-proc
是 #f
, 任何现存的 rtd
和相等性过程之间的关联都会被解除。
第二种形式中, record-type-equal-procedure
返回 rtd
关联的相等性过程,如果不存在,则返回 #f
.
当改变一个 record 类型的相等性过程时,如果这个 record 类型有哈希过程,则需要的话也应该更新,对当前相等性过程判定为相等的任意两个实例,要确保生成相同的哈希值。
过程: (record-equal-procedure record1 record2)
返回: record1
和 record2
的共享相等性过程,如果不存在,则为 #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
是一个过程,则在 rtd
和 hash-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-record
或 make-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>
其中 name
是 record 的美观名字(不是完整的 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)
class
和 type
是可选的,而 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
的辅助关键字。除了在被识别为辅助关键字的上下文中,在其它任何地方引用这些标识符都是违反语法的。 mutable
和 immutable
也是 define-record
的辅助关键字,和 R6RS 中的 define-record-type
一样。
语法: (type-descriptor name)
返回: 与 name
相关联的 record 类型描述符
库: (chezscheme)
name
必须命名一个由 define-record
或 define-record-type
定义的 record 类型。
这个语法形式等价于 R6RS 中 record-type-descriptor
的语法形式。
record 类型描述符在使用 record-reader
和 record-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
.
传入参数 name
和 rtd
时, record-reader
把 rtd
注册为 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-writer
在 rtd
和 procedure
之间建立一个新的关联,从而使过程被用于打印器,以代替给定类型的 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)
其中 class
和 type
是可选的。 field-name
必须是一个符号。如果指定 class
,则必须是符号 immutable
或符号 mutable
。如果提供 immutable
类别指示符,则字段是不可变的;否则,字段是可变的。 type
, 如果提供的话,指定了字段如何表示。类型类型和 174 页上对 define-record
的描述中给出的一样。
如果指定了类型,则字段只能包含指定类型的对象。如果没有指定类型,字段则是 ptr
类型,意思是它可以包含任意 Scheme 对象。
修改字符串 type-name
或 fields
列表或它的任何子列表的程序行为是未定义的。
record 类型描述符可以作为参数传递给 R6RS 中的下列任何过程
record-constructor
,record-predicate
,record-accessor
, andrecord-mutator
,
或 Chez Scheme 中的变体
record-constructor
,record-field-accessor
, andrecord-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-accessor
和 record-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 类型的字段数。指定字段必须是可变的。
修改器接受两个参数, r 和 obj. 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