Skip to content

Latest commit

 

History

History
647 lines (377 loc) · 21.2 KB

07. 对象操作.md

File metadata and controls

647 lines (377 loc) · 21.2 KB

7. 对象操作

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

7.1. R6RS 中缺少的类型判断

过程: (enum-set? obj)

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

库: (chezscheme)

此一谓词应当被定义,但缺失于第 6 修订版中。

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

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

库: (chezscheme)

此一谓词应当被定义,但缺失于第 6 修订版中。

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* 和第 6 修订版中的 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)

这些过程与第 6 修订版中的 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)

这些谓词与第 6 修订版中的对应版本是一样的,只是被扩展为接受一个以上参数,而非两个以上参数。当只传入一个参数时,这些谓词均返回 #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 中,字符串的长度和索引总是定长数。

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

这些谓词与第 6 修订版中的对应版本是一样的,只是被扩展为接受一个以上参数,而非两个以上参数。当只传入一个参数时,这些谓词均返回 #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 必须是精确的非负定长数,且不大于 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 中,向量的长度和索引总是定长数。

所有向量默认都是可变的,包括常量。程序可以通过 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 的长度。

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

参见后面关于全定长数向量 (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)

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

定长数向量的长度和索引总是定长数。

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

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

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

过程: (fxvector? obj)

返回: 如果 obj 是一个定长数向量,则为 #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 ... 组成的定长数向量

库: (chezscheme)

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

过程: (make-fxvector n)

过程: (make-fxvector n fixnum)

返回: 一个长度为 n 的定长数向量

库: (chezscheme)

n 必须是定长数。如果有提供参数 fixnum, 则定长数向量中的每个元素都被初始化为 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 必须是一个非负定长数,且严格小于 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 必须是一个非负定长数,且严格小于 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 中所有元素组成的定长数向量

库: (chezscheme)

list 必须完全由定长数组成。

(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-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 是一个可变的定长数向量,则为 #t, 否则为 #f.

过程: (immutable-fxvector? obj)

返回: 如果 obj 是一个不可变的定长数向量,则为 #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 内容相同的一个不可变定长数向量。

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