上一章介绍了一些Racket的内建数据类型:数字、布尔值、字符串、列表、和过程。本节为数据的简单表提供一个内建数据类型的更完整的覆盖。
3.1 布尔值(Boolean) |
3.2 数值(Number) |
3.3 字符(Character) |
3.4 字符串(Unicode) |
3.5 字节(Byte)和字节字符串(Byte String) |
3.6 符号(Symbol) |
3.7 关键字(Keyword) |
3.8 点对(Pair)和列表(List) |
3.9 向量(Vector) |
3.10 哈希表(Hash Table) |
3.11 格子(Box) |
3.12 无效值(Void)和未定义值(Undefined) |
Racket有表示布尔值的两个重要的常数:#t表示真,#f表示假。大写的#T和#F解析为同样的值,但小写形式是首选。
boolean?程序识别两个布尔常量。然而,在对if、cond、 and、or等等的一个测试表达式的结果里,除了#f之外,任何值都是记为真。
> (= 2 (+ 1 1)) #t
> (boolean? #t) #t
> (boolean? #f) #t
> (boolean? "no") #f
> (if "no" 1 0) 1
一个Racket的数值(number)既可以是精确的也可以是不精确的:
一个精确的数值是:
一个任意大的或任意小的整数,比如:5,99999999999999999或-17;
一个有理数,它是精确的两个任意小的或任意大的整数比,比如:1/2,99999999999999999/2或-3/4;
一个带有精确的实部和虚部(即虚部不为零)的复数,比如:1+2i或1/2+3/4i。
一个 不精确的数值是:
一个数值的一个IEEE浮点表示,比如:2.0或3.14e+87,其中IEEE无穷大和一个非数值编写为:+inf.0,-inf.0和+nan.0(或-nan.0);
一个带有IEEE浮点表示的实部和虚部的复数,比如:2.0+3.0i或-inf.0+nan.0i;作为一种特例,一个带有一个不精确的虚部的不精确的复数可以有一个精确的零实部。
带有一个小数点或指数说明符的不精确数字打印,以及作为整数和分数的精确数字打印。同样的的惯例申请读取数值常量,但#e或#i能够前缀一个数值以强制其解析为一个精确的或不精确的数值。前缀#b、#o和#x指定二进制、八进制和十六进制数的解释。
《The Racket Reference(Racket参考)》文档(4.2 Numbers(数值))有数值的语法的细微之处。
> 0.5 0.5
> #e0.5 1/2
> #x03BB 955
包含一个精确数值的计算产生不精确的结果,以致不精确充当了一种数值方面的污染。注意,然而,Racket没有提供"不精确的布尔值",所以对不精确的数字的比较分支计算却仍然能产生精确的结果。过程exact->inexact和inexact->exact在两种数值类型之间转换。
> (/ 1 2) 1/2
> (/ 1 2.0) 0.5
> (if (= 3.0 2.999) 1 2) 2
> (inexact->exact 0.1) 3602879701896397/36028797018963968
当精确的结果需要作为非有理数实数时,不精确的结果也由像sqrt、log和sin这样的过程产生。Racket仅能表示有理数和带有理数部分的复数。
> (sin 0) ; 有理数... 0
> (sin 1/2) ; 非有理数... 0.479425538604203
在性能而言,带小整数的计算通常是最快的,其中“小”意味着这个合二为一的数值小于有符号数值的机器字长。具有非常大的精确整数或具有非整精确数的计算会比不精确数的计算代价要高昂得多。
(define (sigma f a b) (if (= a b) 0 (+ (f a) (sigma f (+ a 1) b))))
> (time (round (sigma (lambda (x) (/ 1 x)) 1 2000))) cpu time: 75 real time: 117 gc time: 0
8
> (time (round (sigma (lambda (x) (/ 1.0 x)) 1 2000))) cpu time: 0 real time: 28 gc time: 0
8.0
数值类别整数(integer)、有理数(rational)、实数(real)(总是有理数)以及复数(complex)用通常的方法定义,并被过程integer?、rational?、real?以及complex?所验证。一些数学过程只接受实数,但大多数实现了对复数的标准扩展。
> (integer? 5) #t
> (complex? 5) #t
> (integer? 5.0) #t
> (integer? 1+2i) #f
> (complex? 1+2i) #t
> (complex? 1.0+2.0i) #t
> (abs -5) 5
> (abs -5+2i) abs: contract violation
expected: real?
given: -5+2i
> (sin -5+2i) 3.6076607742131563+1.0288031496599335i
=过程为了数值相等而比较数值。如果给定不精确数和精确数去作比较,它在比较之前从本质上将不精确数转换为精确数。相反,eqv?(乃至 equal?)过程比较数值既考虑精确性又考虑数值的相等。
> (= 1 1.0) #t
> (eqv? 1 1.0) #f
当心涉及不精确数的比较,由于其天性会有出人意料的行为。甚至实际上简单的不精确数也许并不意味着你能想到的和他们的意义一致;例如,当一个二进制IEEE浮点数可以精确地表示为1/2时,它可能近似于1/10:
> (= 1/2 0.5) #t
> (= 1/10 0.1) #f
> (inexact->exact 0.1) 3602879701896397/36028797018963968
《The Racket Reference(Racket参考)》文档(4.2 Numbers(数值))有关于数值和数值过程的更多内容。
Racket 字符(character)对应于Unicode标量值(scalar value)。粗略地说,一个标量值是一个无符号整数,表示为21位,并且映射到某种自然语言字符或字符块的某些概念。从技术上讲,一个标量值是一个比Unicode标准中的一个“字符”更简单的概念,但它是一种有许多作用的近似值。例如,任何重音罗马字母都可以表示为一个标量值,就像任何普通的汉字字符一样。
虽然每个Racket字符对应一个整数,但字符数据类型和数值是有区别的。char->integer和integer->char过程在标量值和相应字符之间转换。
一个可打印字符通常打印为以#\后跟着代表字符的形式。一个非打印字符通常打印为以#\u后跟着十六进制数值的标量值的形式。几个字符以特殊方式打印;例如,空格和换行符分别打印为#\space和#\newline。
在《The Racket Reference(Racket参考)》的字符解析文档有字符语法的更好的知识点。
> (integer->char 65) #\A
> (char->integer #\A) 65
> #\λ #\λ
> #\u03BB #\λ
> (integer->char 17) #\u0011
> (char->integer #\space) 32
display过程直接将一个字符写入到当前输出端口(详见《输入和输出》),与用于打印一个字符结果的字符常量语法形成对照。
> #\A #\A
> (display #\A) A
Racket提供了几种对字符的分类和转换的过程。然而,注意某些Unicode字符要只有它们在一个字符串中和一个人所希望的那样转换才行(例如,”ß”的大写转换或者”Σ”的小写转换)。
> (char-alphabetic? #\A) #t
> (char-numeric? #\0) #t
> (char-whitespace? #\newline) #t
> (char-downcase #\A) #\a
> (char-upcase #\ß) #\ß
char=?过程比较两个或多个字符,char-ci=?比较字符但忽略大写。eqv?和equal?过程在字符方面的行为与char=?表现一样;当你更具体地声明正在比较的值是字符时使用char=?。
> (char=? #\a #\A) #f
> (char-ci=? #\a #\A) #t
> (eqv? #\a #\A) #f
在《The Racket Reference(Racket参考)》的字符部分中提供字符和字符过程的更多信息。
一个字符串(string)是一个固定长度的字符(characters)数组。它使用双引号打印,在字符串中的双引号和反斜杠字符是用反斜杠转义。其它普通的字符串转义被支持,包括\n用于一个换行,\r用于一个回车,使用\后边跟着多达三个八进制数字实现八进制转义,以及用\u(多达四位数)实现十六进制转义。在打印字符串时通常用\u显示一个字符串中的不可打印字符。
在《Racket参考》中的“读取字符串(Reading Strings)”文档有关于字符串语法的更好的知识点。
display过程直接将一个字符串中的字符写入当前输出端口(见《输入和输出》),在字符串常量语法对比中用于打印一个字符串结果。
> "Apple" "Apple"
> "\u03BB" "λ"
> (display "Apple") Apple
> (display "a \"quoted\" thing") a "quoted" thing
> (display "two\nlines")
two
lines
> (display "\u03BB") λ
一个字符串可以是可变的也可以是不可变的;作为表达式直接编写的字符串是不可变的,但大多数其它字符串是可变的。make-string过程创建一个给定一个长度和可选填充字符的可变字符串。string-ref过程从一个字符串(用基于0的索引)中访问一个字符。string-set!过程在一个可变字符串中更改一个字符。
> (string-ref "Apple" 0) #\A
> (define s (make-string 5 #\.)) > s "....."
> (string-set! s 2 #\λ) > s "..λ.."
字符串排序和状态操作通常是区域无关(locale-independent)的;也就是说,它们对所有用户都采用相同的工作方式。一些区域相关(locale-dependent)的操作被提供,它们允许字符串折叠和排序的方式取决于最终用户的区域设置。如果你在排序字符串,例如,如果排序结果应该在机器和用户之间保持一致,使用string或string-ci,但如果排序纯粹是为一个最终用户整理字符串,使用string-locale或string-locale-ci。
> (string "apple" "Banana") #f
> (string-ci "apple" "Banana") #t
> (string-upcase "Straße") "STRASSE"
> (parameterize ([current-locale "C"]) (string-locale-upcase "Straße")) "STRAßE"
对于使用纯粹的ASCII、使用原始字节或编码/解码Unicode字符串为字节,使用字节字符串(byte strings)。
在《Racket参考》中的字符串(strings)部分提供更多字符串和字符串过程的信息。
一个字节(byte)是一个在0到255之间的精确整数。byte?判断识别表示字节的数字。
> (byte? 0) #t
> (byte? 256) #f
一个字节字符串(byte string)类似于一个字符串——参见《字符串(Unicode)》,但它的内容是字节序列而不是字符。字节字符串可用于处理纯ASCII文本而不是Unicode文本的应用程序中。一个字节字符串的打印形式特别支持这样使用,因为一个字节字符串打印像字节字符串的ASCII解码,但有一个#前缀。在字节字符串中不可打印的ASCII字符或非ASCII字节用八进制表示法编写。
在《Racket参考》中的“读取字符串(Reading Strings)”文档有关于字节字符串语法的更好的知识点。
> #"Apple" #"Apple"
> (bytes-ref #"Apple" 0) 65
> (make-bytes 3 65) #"AAA"
> (define b (make-bytes 2 0)) > b #"\0\0"
> (bytes-set! b 0 1) > (bytes-set! b 1 255) > b #"\1\377"
一个字节字符串的display表写入其原始字节到当前输出端口(详见《输入和输出》部分)。从技术上讲,一个通常(即,字符)的display字符串打印字符串的UTF-8编码到当前输出端口,因为输出是以字节为单位的最终定义;然而一个字节字符串的display用无编码的方式写入原始字节。按同样的思路,当这个文档显示输出时,它严格说来是显示输出的UTF-8编码格式。
> (display #"Apple") Apple
> (display "\316\273") ; 等同于"λ" λ
> (display #"\316\273") ; λ的UTF-8编码 λ
对于在字符串和字节字符串之间的显式转换,Racket直接支持三种编码:UTF-8,Latin-1和当前的本地编码。字节到字节转换(特别是转换到UTF-8和从UTF-8转换来)的通用工具弥合了支持任意字符串编码的差异分歧。
> (bytes->string/utf-8 #"\316\273") "λ"
> (bytes->string/latin-1 #"\316\273") "λ"
> (parameterize ([current-locale "C"]) ; C局部支持ASCII, (bytes->string/locale #"\316\273")) ; 仅仅,这样…… bytes->string/locale: byte string is not a valid encoding
for the current locale
byte string: #"\316\273"
> (let ([cvt (bytes-open-converter "cp1253" ; 希腊代码页 "UTF-8")] [dest (make-bytes 2)]) (bytes-convert cvt #"\353" 0 1 dest) (bytes-close-converter cvt) (bytes->string/utf-8 dest)) "λ"
在《Racket参考》里的“字节字符串(Byte Strings)”部分提供了关于字节字符串和字节字符串函数的更详尽内容。
一个符号(symbol)是一个原子值,它像一个前面的标识符那样以'前缀打印。一个以'开始并带一个标识符的表达式产生一个符号值。
> 'a 'a
> (symbol? 'a) #t
对于字符的任何序列,正好有一个相应的符号被保留(interned);调用string->symbol过程或者read一个语法标识符,产生一个保留符号。由于保留符号可以被用eq?(或这样:eqv?或equal?)方便地比较,所以它们作为方便的值用于标签和枚举。
符号是区分大小写的。通过使用一个#ci前缀或其它方式,读取器能够被要求去折叠容器序列以获得一个符号,但是读取器通过默认方式保护容器。
> (eq? 'a 'a) #t
> (eq? 'a (string->symbol "a")) #t
> (eq? 'a 'b) #f
> (eq? 'a 'A) #f
> #ci'A 'a
任何字符串(或者说,任何字符序列)都可以提供给string->symbol以获得对应的符号。对于读取器输入来说,任何字符都可以直接出现在一个标识符里,空白和以下特殊字符除外:
( ) [ ] { } " , ' ` ; # | \
实际上,#仅仅不允许在一个符号开始位置,并且也仅仅不允许%在最后位置;除此之外,#是被允许的。此外,.本身不是一个符号。
空格或特殊字符可以通过用|或\引用包含进一个标识符里。这些引用机制用于包含特殊字符或可能额外看起来像数字的标识符的打印表中。
> (string->symbol "one, two") '|one, two|
> (string->symbol "6") '|6|
在《Racket参考》中的“读取符号(Reading Symbols)”文档有关于符号语法的更好的知识点。
write函数打印一个没有一个'前缀的符号。一个符号的display表与对应的字符串相同。
> (write 'Apple) Apple
> (display 'Apple) Apple
> (write '|6|) |6|
> (display '|6|) 6
gensym和string->uninterned-symbol过程生成新的非保留(uninterned)符号,它不等同于(比照eq?)任何先前的保留或非保留符号。非保留符号作为新标签是有用的,它不会与其它任何值混淆。
> (define s (gensym)) > s 'g42
> (eq? s 'g42) #f
> (eq? 'a (string->uninterned-symbol "a")) #f
在《Racket参考》中的“符号(Symbols)”文档有关于符号的更多信息。
一个关键字(keyword)值类似于一个符号(详见《符号(Symbol)》),但它的打印形式是用#:进行前缀。
在《Racket参考》里的“读取关键字”(Reading Keywords)文档有关于关键字的语法更好的知识点。
> (string->keyword "apple") '#:apple
> '#:apple '#:apple
> (eq? '#:apple (string->keyword "apple")) #t
更确切地说,一个关键字类似于一个标识符;以同样的方式,一个标识符可以被引用以生成一个符号,一个关键字可以被引用以生成一个值。在这两种情况下都使用同一术语“关键字”,但有时我们使用关键字值(keyword value)去更具体地针对一个引用关键字表达式的结果或使用string->keyword的结果。一个非引用关键字不是一个表达式,只是作为一个非引用标识符,不产生一个符号:
> not-a-symbol-expression not-a-symbol-expression: undefined;
cannot reference undefined identifier
> #:not-a-keyword-expression eval:2:0: #%datum: keyword used as an expression
in: #:not-a-keyword-expression
尽管它们有相似之处,但关键字的使用方式不同于标识符或符号。关键字是为了使用(不带引号)作为参数列表和在特定的句法形式的特殊标记。运行时的标记和枚举,而不是关键字用符号。下面的示例说明了关键字和符号的不同角色。
> (define dir (find-system-path 'temp-dir)) ; not '#:temp-dir
> (with-output-to-file (build-path dir "stuff.txt") (lambda () (printf "example\n")) ; 可选的#:mode参数可以是'text或'binary #:mode 'text ; 可选的#:exists参数可以是'replace、'truncate、... #:exists 'replace)
一个点对(pair)把两个任意值结合。cons过程构建点对,car和cdr过程分别提取点对的第一和第二个点对元素。pair?判断识别点对。
一些点对通过圆括号包围两个点对元素的打印形式来打印,在开始位置放置一个',并在元素之间放置一个.。
> (cons 1 2) '(1 . 2)
> (cons (cons 1 2) 3) '((1 . 2) . 3)
> (car (cons 1 2)) 1
> (cdr (cons 1 2)) 2
> (pair? (cons 1 2)) #t
一个列表(list)是一个点对的组合,它创建一个链表。更确切地说,一个列表要么是空列表null,要么是个点对,其第一个元素是一个列表元素,第二个元素是一个列表。list?判断识别列表。null?判断识别空列表。
一个列表通常打印为一个'后跟一对括号包裹列表元素。
> null '()
> (cons 0 (cons 1 (cons 2 null))) '(0 1 2)
> (list? null) #t
> (list? (cons 1 (cons 2 null))) #t
> (list? (cons 1 2)) #f
当一个列表或点对的其中一个元素不能写成一个quote(引用)值时,使用list或cons打印。例如,一个用srcloc构建的值不能使用quote来编写,应该使用srcloc来编写:
> (srcloc "file.rkt" 1 0 1 (+ 4 4)) (srcloc "file.rkt" 1 0 1 8)
> (list 'here (srcloc "file.rkt" 1 0 1 8) 'there) (list 'here (srcloc "file.rkt" 1 0 1 8) 'there)
> (cons 1 (srcloc "file.rkt" 1 0 1 8)) (cons 1 (srcloc "file.rkt" 1 0 1 8))
> (cons 1 (cons 2 (srcloc "file.rkt" 1 0 1 8))) (list* 1 2 (srcloc "file.rkt" 1 0 1 8))
也参见list*。
如最后一个例子所示,list*是用来缩略一系列不能使用list缩写的cons。
write和display函数不带一个前导'、cons、list或list*打印一个点对或一个列表。对于一个点对或列表来说write和display没有区别,除非它们运用于列表的元素:
> (write (cons 1 2)) (1 . 2)
> (display (cons 1 2)) (1 . 2)
> (write null) ()
> (display null) ()
> (write (list 1 2 "3")) (1 2 "3")
> (display (list 1 2 "3")) (1 2 3)
对于列表来说最重要的预定义过程是遍历列表元素的那些过程:
> (map (lambda (i) (/ 1 i)) '(1 2 3)) '(1 1/2 1/3)
> (andmap (lambda (i) (i . < . 3)) '(1 2 3)) #f
> (ormap (lambda (i) (i . < . 3)) '(1 2 3)) #t
> (filter (lambda (i) (i . < . 3)) '(1 2 3)) '(1 2)
> (foldl (lambda (v i) (+ v i)) 10 '(1 2 3)) 16
> (for-each (lambda (i) (display i)) '(1 2 3)) 123
> (member "Keys" '("Florida" "Keys" "U.S.A.")) '("Keys" "U.S.A.")
> (assoc 'where '((when "3:30") (where "Florida") (who "Mickey"))) '(where "Florida")
在《Racket参考》中的“点对和列表(Pairs and Lists)”提供更多有关点对和列表的信息。
点对是不可变的(与Lisp传统相反),并且pair?和list?仅识别不可变的点对和列表。mcons过程创建一个可变点对(mutable pair),它配合set-mcar!和set-mcdr!,及mcar和mcdr进行操作。一个可变点对用mcons打印,而write和display使用{和}打印:
> (define p (mcons 1 2)) > p (mcons 1 2)
> (pair? p) #f
> (mpair? p) #t
> (set-mcar! p 0) > p (mcons 0 2)
> (write p) {0 . 2}
在《Racket参考》中的“可变点对和列表(Mutable Pairs and Lists)”中提供关于可变点对的更多信息。
一个向量(vector)是任意值的一个固定长度数组。与一个列表不同,一个向量支持常量时间访问和它的元素更新。
一个向量打印类似于一个列表——作为其元素的一个括号序列——但一个向量要在'之后加前缀#,或如果它的元素不能用引号表示则使用vector表示。
对于作为一个表达式的一个向量,可以提供一个可选长度。同时,一个向量作为一个表达式隐式地为它的内容quote(引用)这个表,这意味着在一个向量常数中的标识符和括号表代表符号和列表。
在《Racket参考》中的“读取向量(Reading Vectors)”文档有向量的语法更好的知识点。
> #("a" "b" "c") '#("a" "b" "c")
> #(name (that tune)) '#(name (that tune))
> #4(baldwin bruce) '#(baldwin bruce bruce bruce)
> (vector-ref #("a" "b" "c") 1) "b"
> (vector-ref #(name (that tune)) 1) '(that tune)
像字符串一样,一个向量要么是可变的,要么是不可变的,向量直接编写为表达式是不可变的。
向量可以通过vector->list和list->vector转换成列表,反之亦然。这种转换在与对列表的预定义过程相结合中是特别有用的。当分配额外的列表似乎太昂贵时,考虑使用像for/fold的循环表,它像列表一样识别向量。
> (list->vector (map string-titlecase (vector->list #("three" "blind" "mice")))) '#("Three" "Blind" "Mice")
在《Racket参考》中的“向量(vectors)”部分提供有关向量和向量过程的更多内容。
一个哈希表(hash table)实现了从键到值的一个映射,其中键和值都可以是任意的Racket值,以及对表的访问和更新通常是常量时间操作。键的比较使用equal?、eqv?或eq?,取决于哈希表创建方式是否为make-hash、make-hasheqv或make-hasheq。
> (define ht (make-hash)) > (hash-set! ht "apple" '(red round)) > (hash-set! ht "banana" '(yellow long)) > (hash-ref ht "apple") '(red round)
> (hash-ref ht "coconut") hash-ref: no value found for key
key: "coconut"
> (hash-ref ht "coconut" "not there") "not there"
hash、hasheqv和hasheq函数从键和值的一个初始设置创建不可变哈希表,其中每个值作为它键后边的一个参数提供。不可变哈希表可用hash-set扩展,它在恒定时间里产生一个新的不可变哈希表。
> (define ht (hash "apple" 'red "banana" 'yellow)) > (hash-ref ht "apple") 'red
> (define ht2 (hash-set ht "coconut" 'brown)) > (hash-ref ht "coconut") hash-ref: no value found for key
key: "coconut"
> (hash-ref ht2 "coconut") 'brown
一个原义的不可变哈希表可以通过使用#hash(对基于equal?的表)、#hasheqv(对基于eqv?的表)或#hasheq(对基于eq?的表)编写为一个表达式。一个带括号的序列必须紧跟着#hash、#hasheq或#hasheqv,其中每个元素是一个带点的键–值对。这个#hash等等这些表都隐含的quote它们的键和值的子表。
> (define ht #hash(("apple" . red) ("banana" . yellow))) > (hash-ref ht "apple") 'red
在《Racket参考》的“读取哈希表(Reading Hash Tables)”文档有关于哈希表原义的语法更好的知识点。
可变和不可变的哈希表都像不可变哈希表一样打印,否则如果所有的键和值可以用quote表示或者使用hash、hasheq或hasheqv,那么使用一个带引用的#hash、#hasheqv或#hasheq表。
> #hash(("apple" . red) ("banana" . yellow)) '#hash(("apple" . red) ("banana" . yellow))
> (hash 1 (srcloc "file.rkt" 1 0 1 (+ 4 4))) (hash 1 (srcloc "file.rkt" 1 0 1 8))
一个可变哈希表可以选择性地弱方式(weakly)保留其键,因此仅仅只要在其它地方保留键,每个映射都被保留。
> (define ht (make-weak-hasheq)) > (hash-set! ht (gensym) "can you see me?") > (collect-garbage) > (hash-count ht) 0
请注意,只要对应的键是可访问的,即使是一个弱哈希表也会强健地保留它的值。当一个值指回到它的键,就造成了一个两难的依赖,以致这个映射永久被保留。要打破这个循环,映射键到一个暂存值(ephemeron),它用它的键(除这个哈希表的隐性配对之外)配对值。
在《Racket参考》中的“星历(ephemerons)”文档有关于使用ephemerons更好的知识点。
> (define ht (make-weak-hasheq))
> (let ([g (gensym)]) (hash-set! ht g (list g))) > (collect-garbage) > (hash-count ht) 1
> (define ht (make-weak-hasheq))
> (let ([g (gensym)]) (hash-set! ht g (make-ephemeron g (list g)))) > (collect-garbage) > (hash-count ht) 0
在《Racket参考》中的“哈希表(Hash Tables)”会提供关于哈希表和哈希表过程更多的信息。
一个格子(box)是一个单元素向量。它可以打印成一个带引用的#&后边跟着这个格子值的打印表。一个#&表也可以用来作为一个表达,但由于作为结果的格子是常量,它实际上没有使用。
> (define b (box "apple")) > b '#&"apple"
> (unbox b) "apple"
> (set-box! b '(banana boat)) > b '#&(banana boat)
在《Racket参考》的“格子(Boxes)”提供关于格子和格子过程的更多信息。
某些过程或表达式表不需要一个结果值。例如,display过程被别用仅为写输出的副作用。在这样的情况下,结果值通常是一个特殊的常量,它打印为#
void过程接受任意数量的参数并返回#
> (void) > (void 1 2 3) > (list (void)) '(#
)
undefined常量,它打印为#
在某些情况下,undefined结果仍然可以通过shared表产生。
(define (fails) (define x x) x)
> (fails) x: undefined;
cannot use before initialization