11. 基本库
本章描述Scheme的(rnrs base (6))库,其导出了很多通常和Scheme一起的过程和语法绑定。
第11.20小节定义了来自(rnrs base (6))库的从结构上识别尾调用和尾上下文的规则。
11.1. 基本类型
没有变量满足下列谓词中的多个:
boolean? pair?
symbol? number?
char? string?
vector? procedure?
null?
这些谓词定义了基本的类型布尔,点对,符号,数字,字符,字符串,向量,以及过程。此外,空表是一个特殊的对象,它有自己的类型。
注意,尽管有一个单独的布尔类型,但是,任何Scheme值在条件测试中都可以被当作一个布尔值使用;见5.7小节。
11.2. 定义
定义出现在一个<top-level body>(8.1小节)中,<library body>的最上面(7.1小节),或<body>的最上面(11.3小节)。
一个<definition>可以是一个变量定义(11.2.1小节),或一个关键词定义(11.2.1小节)。扩展到定义或定义组(打包在一个begin, let-syntax, 或letrec-syntax形式中见11.4.7小节)中的宏使用也可以出现在任何其它定义可以出现的地方。
11.2.1. 变量定义
本节描述的define形式是一个用作创建变量绑定的<definition>,且可以出现在其它定义可以出现的任何地方。
(define <variable> <expression>) |
语法 |
(define <variable>) |
语法 |
(define (<variable> <formals>) <body>) |
语法 |
(define (<variable> . <formal>) <body>) |
语法 |
define的第一个形式绑定<variable>到一个新的位置,然后将<expression>的值赋值给这个位置。
(define add3
(lambda (x) (+ x 3)))
(add3 3) ⇒ 6
(define first car)
(first '(1 2)) ⇒ 1
<expression>的继续不应该被调用多于一次。
实现的责任:实现应当检测<expression>的继续被调用多于一次的情况。如果实现检测到这个的话,它必须抛出一个条件类型是&assertion的异常。
define的第二种形式等价于
(define <variable> <unspecified>)
其中<unspecified>是一个返回未定义值得无副作用的表达式。
在define的第三个形式中,<formals>必须或者是零个或多个变量的序列,或者是一个或多个变量的序列,其跟着一个点.和另一个变量(就像在一个lambda表达式中一样,见11.4.2小节)。这个形式等价于
(define <variable>
(lambda (<formals>) <body>)).
在define的第四个形式中,<formal>必须是一个单独的变量。这个形式等价于
(define <variable>
(lambda <formal> <body>)).
11.2.2. 语法定义
本节描述的define-syntax形式是一个用作创建关键词绑定的<definition>,其可以出现在任何其它定义可以出现的地方。
(define-syntax <keyword> <expression>) 语法
绑定<keyword>到<expression>的值,其必须在宏扩展的阶段求值得到一个转换器。宏转换器可以使用11.19小节描述的syntax-rules和identifier-syntax形式创建。见库的第12.3小节,那儿有一个转换器更全面的描述。
通过define-syntax建立的关键词绑定在其出现的整个内部都是可见的,除非被其它绑定覆盖外都是可见的,就像define建立的变量绑定一样。通过一组定义建立的绑定,不管是关键词还是变量定义,在定义它们自己中是可见的。
实现的责任:实现应该检测<expression>的值是不是一个适当的转换器。
例子:
(let ()
(define even?
(lambda (x)
(or (= x 0) (odd? (- x 1)))))
(define-syntax odd?
(syntax-rules ()
((odd? x) (not (even? x)))))
(even? 10)) ⇒ #t
从左到右的处理顺序(第10章)的言外之意是一个定义可以影响后续的形式是否也是一个定义。
例子:
(let ()
(define-syntax bind-to-zero
(syntax-rules ()
((bind-to-zero id) (define id 0))))
(bind-to-zero x)
x) ⇒ 0
任何出现在let表达式外面的bind-to-zero的绑定都不会影响其行为。
11.3. 内部
lambda, let, let*, let-values, let*-values, letrec, 或letrec*表达式的<body>,或由零个或多个跟着一个或多个表达式的定义。
<definition> ... <expression1> <expression2> ...
通过一个定义定义的标识符在<body>中局部的。也就是说,标识符被绑定到,且绑定的作用域是这个<body>(见第5.2小节)。
例子
(let ((x 5))
(define foo (lambda (y) (bar x y)))
(define bar (lambda (a b) (+ (* a b) a)))
(foo (+ x 3))) ⇒ 45
当begin, let-syntax, 或letrec-syntax形式先于第一个表达式出现在内部的时候,它们被拼接到内部;见第11.4.7小节。内部的一些或所以,包括begin, let-syntax, 或letrec-syntax形式里面的部分,可以通过一个宏使用指定(见第9.2小节)。
一个包含变量定义的被扩展的<body>(见第10章)总是可以被转换成一个等价的letrec*表达式。比如,上面例子中的let表达式等价于
(let ((x 5))
(letrec* ((foo (lambda (y) (bar x y)))
(bar (lambda (a b) (+ (* a b) a))))
(foo (+ x 3))))
11.4. 表达式
本节的条目描述(rnrs base (6))库中的表达式,除了第9.1小节描述的基本表达式类型,其可以出现在<expression>句法变量的位置中。
11.4.1. 引用(Quotation)
(quote <datum>) 语法
语法:<Datum>应该是一个句法数据。
语义:quote <datum>)的值是<datum>表示的数据值(见第4.3小节)。这个符号被用作包含常量。
(quote a) ⇒ a
(quote #(a b c)) ⇒ #(a b c)
(quote (+ 1 2)) ⇒ (+ 1 2)
正如第4.3.5小节所说,(quote <datum>)可以使用缩写'<datum>:
'"abc" ⇒ "abc"
'145932 ⇒ 145932
'a ⇒ a
'#(a b c) ⇒ #(a b c)
'() ⇒ ()
'(+ 1 2) ⇒ (+ 1 2)
'(quote a) ⇒ (quote a)
''a ⇒ (quote a)
正如第5.10小节所说,常量是不可变得。
注意:一个quote表达式值的不同常数可以共享相同的位置。
11.4.2 过程
(lambda <formals> <body>) 语法
语法:<Formals>必须是一个下面描述的形参(formal parameter)列表,且<body>必须和第11.3小节描述的一样。
语义:lambda表达式的值是过程。当lambda表达式被求值时的有效环境被作为过程的一部分被记忆。当过程随后以一些参数被调用的时候,lambda表达式被求值时使用的环境通过绑定参数列表中的变量到新的位置的方式被扩展,且相应的实参值被存储到那些位置。然后,lambda表达式内部的表达式(其可能包含定义,因此可能表现为一个letrec*形式,见第11.3小节)在扩展的环境中被顺序地求值。内部最后一个表达式的结果作为过程调用的结果被返回。
(lambda (x) (+ x x)) ⇒ 一个过程
((lambda (x) (+ x x)) 4) ⇒ 8
((lambda (x)
(define (p y)
(+ y 1))
(+ (p x) x))
5) ⇒ 11
(define reverse-subtract
(lambda (x y) (- y x)))
(reverse-subtract 7 10) ⇒ 3
(define add4
(let ((x 4))
(lambda (y) (+ x y))))
(add4 6) ⇒ 10
<Formals>必须有下列的形式之一:
\(\texttt{(<variable$_1$> ...)}\):过程拥有固定数量的参数。当过程被调用时,参数将被存储在相应变量的绑定中。<variable>:过程拥有任意数量的参数。当过程被调用时,实参的序列被转换为一个新创建的表,该表存储在的绑定中。 \(\texttt{(<variable$_1$> ... <variable$_n$> . <variable$_{n+1}$>)}\):如果一个由空格分隔的.出现在最后一个变量之前,该过程就拥有n个或更多个参数,这里的n是句点前面形参的个数(至少要有一个)。存储在最后一个参数绑定中的值是一个新创建的表。除了已和其他形参匹配的所有其他实参外,剩余的实参都被存入该表中。
((lambda x x) 3 4 5 6) ⇒ (3 4 5 6)
((lambda (x y . z) z)
3 4 5 6) ⇒ (5 6)
在<formals>中任何<variable>必须不能出现超过一次。
11.4.3. 条件表达式(Conditionals)
(if <test> <consequent> <alternate>) |
语法 |
(if <test> <consequent>) |
语法 |
语法:<Test>, <consequent>, 和<alternate>必须是表达式。
语义:if表达式按如下方式计算:首先,计算<test>的值。如果产生一个真值(见第5.7小节),然后<consequent>被计算,且它的值被返回。否则,<alternate>被计算,且它的值被返回。如果<test>产生#f且没有指定<alternate>,那么,表达式的结果是未定义的。
(if (> 3 2) 'yes 'no) ⇒ yes
(if (> 2 3) 'yes 'no) ⇒ no
(if (> 3 2)
(- 3 2)
(+ 3 2)) ⇒ 1
(if #f #f) ⇒ unspecified
<Consequent>和<alternate>表达式在尾上下文中,如果if表达式它自己在的话;见第11.20小节。
11.4.4. 赋值
(set! <variable> <expression>) 语法
<Expression>被计算,且结果值被存进<variable>绑定的位置。
(let ((x 2))
(+ x 1)
(set! x 4)
(+ x 1)) ⇒ 5
如果<variable>引用一个不可修改的绑定,那么这时一个语法错误。
注意:标识符set!同时以级别1被导出。见第11.19小节。
11.4.5. 派生条件表达式(Derived conditionals)
(cond <cond clause1> <cond clause2> ...) |
语法 |
=> |
辅助语法 |
else |
辅助语法 |
语法:每一个 <cond clause>必须是形式
(<test> <expression1> ...)
其中<test>是一个表达式。或有另一种选择,一个<cond clause>可以是形式
(<test> => <expression>)
最后一个<cond clause>可以是一个“else子句”,其有形式
(else <expression1> <expression2> ...)。
语义:一个cond表达式通过以下方式求值,按顺序连续地对<test>表达式进行求值直到它们其中一个的值是真值(见第5.7小节)。当一个<test>的值是真值的时候,就会顺序地对它<cond clause>中剩余的<expression>进行求值,且<cond clause>中最后一个<expression>的结果会作为整个cond表达式的结果被返回。如果被选择的<cond clause>只包含<test>且没有<expression>,那么<test>的值会作为结果被返回。如果被选择的<cond clause>使用=>辅助形式,那么<expression>被计算。它的值必须是一个过程。这个过程必须接受一个参数;它被以<test>的值调用,且过程返回的值作为cond表达式的值返回。如果所有的<test>的值都是#f,并且没有else子句,那么条件表达式返回未定义的值;如果有一个else子句,那么它的<expression>被计算,且最后一个的值被返回。
(cond ((> 3 2) 'greater)
((< 3 2) 'less)) ⇒ greater
(cond ((> 3 3) 'greater)
((< 3 3) 'less)
(else 'equal)) ⇒ equal
(cond ('(1 2 3) => cadr)
(else #f)) ⇒ 2
对于一个有下列的形式之一的<cond clause>
(<test> <expression1> ...)
(else <expression1> <expression2> ...)
最后一个<expression>在尾上下文中,如果cond形式它自己在的话。一个如下形式的<cond clause>
(<test> => <expression>)
来自<expression>求值结果的过程的(隐式)调用在尾上下文中如果cond形式它自己在的话。见第11.20小节。
一个更简单形式的cond的参考定义可以在附录B中被找到。
\(\texttt{(case <key> <case clause$_1$> <case clause$_2$> ...)}\) 语法
语法:<Key>必须是一个表达式。每一个<case clause>必须有下列形式之一:
((<datum\(_1\)> ...) <expression\(_1\)> <expression\(_2\)> ...)
(else <expression\(_1\)> <expression\(_2\)> ...)
第二个形式,其指定一个“else子句”,只可以作为最后一个<case clause>出现。每一个<datum>是一些对象的一个外部表示。<Datum>表示的数据不需要有区别。
语义:一个case按如下求值。<key>被求值,且它的结果被和每个<case clause>中的<datum>表示的数据轮流比较,比较的依据是eqv?(见第11.5小节),以从左到右的顺序在整个子句的集合中进行。如果求值后<key>的值等于一个<case clause>的数据,对应的表达式被从左向右地求值,且<case clause>最后一个表达式的结果将作为case表达式的结果被返回。如果求值后的<key>的值和每个子句中的数据都不一样,那么如果有一个else子句的话,它的表达式被计算且最后一个的结果作为case表达式的结果被返回;否则case表达式的返回未定义的值。
; 本示例已根据勘误表修改
(case (* 2 3)
((2 3 5 7) 'prime)
((4 6 8 9) 'composite)) ⇒ composite
(case (car '(c d))
((a) 'a)
((b) 'b)) ⇒ unspecified
(case (car '(c d))
((a e i o u) 'vowel)
((w y) 'semivowel)
(else 'consonant)) ⇒ consonant
一个<case clause>的最后一个<expression>在尾上下文中,如果case表达式它自己在的话;见第11.20小节。
\(\texttt{(and <test$_1$> ...)}\) 语法
语法:<Test>必须是一个表达式。
语义:如果没有<test>,#t被返回。否则<test>表达式被从左向右地求值,直到一个<test>返回#f,或到达最后一个<test>。前一种情况下,and表达式在不计算剩余表达式的情况下返回#f。后一种情况,最后一个表达式被计算且它的值被返回。
(and (= 2 2) (> 2 1)) ⇒ #t
(and (= 2 2) (< 2 1)) ⇒ #f
(and 1 2 'c '(f g)) ⇒ (f g)
(and) ⇒ #t
and关键词可以使用syntax-rules(见第11.19小节)根据if进行定义,如下所示:
(define-syntax and
(syntax-rules ()
((and) #t)
((and test) test)
((and test1 test2 ...)
(if test1 (and test2 ...) #f))))
最后一个<test>表达式在尾上下文中,如果and表达式它自己在的话;见第11.20小节。
\(\texttt{(or <test$_1$> ...)}\) 语法
语法:<Test>必须是表达式。
语义:如果没有<test>,那么#f被返回。否则,<test>按照从左到右的顺序被求值,直到一个<tesst>返回真值val(见第5.7小节),或到达最后一个<test>。在前一种情况,or表达式在不计算剩余表达式的情况下返回val。后一种情况,最后一个表达式被求值,且它的值被返回。
(or (= 2 2) (> 2 1)) ⇒ #t
(or (= 2 2) (< 2 1)) ⇒ #t
(or #f #f #f) ⇒ #f
(or '(b c) (/ 3 0)) ⇒ (b c)
or关键词可以使用syntax-rules(见第11.19小节)根据if进行定义,如下所示:
(define-syntax or
(syntax-rules ()
((or) #f)
((or test) test)
((or test1 test2 ...)
(let ((x test1))
(if x x (or test2 ...))))))
最后一个<test>表达式在尾上下文中,如果or表达式它自己也在的话;见第11.20小节。
11.4.6. 绑定结构
本节描述的绑定结构为变量创建本地绑定,它们只可以在一个限定的区域可见。结构let, let*, letrec, 和letrec*的语法是一样的,但是它们为它们的变量绑定建立的作用域(region)(见5.2小节)是不一样的,且绑定的值计算的顺序是不一样的。在一个let表达式中,初始值被计算在任何变量被绑定之前;在一个let*表达式中,绑定和求值依次地执行。在一个letrec或letrec*表达式中,当所有的绑定正在被计算的时候所以的绑定生效,因此允许相互递归的定义。在一个letrec表达式中,初始值在赋值给变量之前被计算;在一个letrec*中,计算和赋值依次地进行。
此外,绑定结构let-values和let*-values使得let和let*更加通用,起允许多个变量绑定到值是多个值得表达式的结果。在建立作用域的方面,它们类似于let和let*:在一个let-values表达式中,初始值被计算在任何变量被绑定之前;在一个let*-values表达式中,绑定依次进行。
本节提到的所有绑定形式根据更简单形式的参考定义可以在附录B中找到。
(let <bindings> <body>) 语法
语法:<Bindings>必须有形式
\(\texttt{((<variable$_1$> <init$_1$>) ...)}\),
其中,每一个<init>是一个表达式,且<body>如第11.3小节描述。在<variable>中任何变量不能出现超过一次。
语义:<Init>在当前的环境被求值(以一个未定义的顺序),<variable>被绑定到存有结果的新鲜的位置,<body>在扩展后的环境被求值,且<body>的最后一个表达式的值被返回。每个<variable>的绑定都将<body>作为它的作用域。
(let ((x 2) (y 3))
(* x y)) ⇒ 6
(let ((x 2) (y 3))
(let ((x 7)
(z (+ x y)))
(* z x))) ⇒ 35
另参见命名let,见第11.16小节。
(let* <bindings> <body>) 语法
语义:<Bindings>必须是形式
\(\texttt{((<variable$_1$> <init$_1$>) ...)}\),
其中,每一个<init>是一个表达式,且<body>如第11.3小节描述。
语义:let*形式类似于let,但是<init>的求值和绑定的创建是从左向右顺序进行的,但其作用域不但包括<body>,还包括其右边的部分。因此,第二个<init>在第一个绑定可见且初始化的环境中被计算,以此类推。
(let ((x 2) (y 3))
(let* ((x 7)
(z (+ x y)))
(* z x))) ⇒ 70
注意:通过let表达式绑定的变量必须是不一样的,而通过let*表达式绑定的变量则没有这个限制。
(letrec <bindings> <body>) 语法
语法:<Bindings>必须有形式
\(\texttt{((<variable$_1$> <init$_1$>) ...)}\),
其中,每一个<init>是一个表达式,且<body>如第11.3小节描述。在<variable>中任何变量不能出现超过一次。
语义:<variable>被绑定到新的位置,<init>在结果环境中被求值(以一些未定义的顺序),每一个<variable>被分配给对应的<init>的结果,<body>在结果环境中被求值,且<body>中最后一个表达式的值被返回。每个<variable>的绑定将整个letrec表达式作为它的作用域,这使得定义相互递归的过程成为可能。
(letrec ((even?
(lambda (n)
(if (zero? n)
#t
(odd? (- n 1)))))
(odd?
(lambda (n)
(if (zero? n)
#f
(even? (- n 1))))))
(even? 88))
⇒ #t
在不赋值或引用任何<variable>的值的情况下,计算每个<init>应该是可能的。在letrec的大部分常规使用中,所有的<init>是lambda表达式,此时限制被自动满足。另一个限制是,每个<init>的继续不能被调用多于一次。
实现的责任:实现必须在<init>表达式求值(使用一个特定的求值顺序且顺序<init>表达式的值)期间检测<variable>的引用。如果实现检测到这样一个限制的违反,它必须抛出一个条件类型是&assertion的异常。实现可以也可以不检测每个<init>的继续是否被调用多于一次。可是,如果实现检测到的话,它必须抛出一个条件类型是&assertion的异常。
(letrec* <bindings> <body>) 语法
语法:<Bindings>必须有形式
\(\texttt{((<variable$_1$> <init$_1$>) ...)}\),
其中,每一个<init>是一个表达式,且<body>如第11.3小节描述。在<variable>中任何变量不能出现超过一次。
语义:<Variable>被绑定到新鲜的位置,每个<variable>被按从左向右的顺序分配给对应的<init>的求值结果,<body>在结果环境中被求值,且<body>中最后一个表达式的值被返回。尽管求值和赋值的顺序是从左向右的,但是每一个<variable>的绑定将整个letrec*表达式作为其作用域,这使得定义相互递归的过程成为可能。
(letrec* ((p
(lambda (x)
(+ 1 (q (- x 1)))))
(q
(lambda (y)
(if (zero? y)
0
(+ 1 (p (- y 1))))))
(x (p 5))
(y x))
y)
⇒ 5
在不赋值或引用对应的<variable>或任何在<bindings>跟随它的任何绑定的<variable>的值的情况下,计算每个<init>必须是可能的。另一个限制是每个<init>的继续不能被调用多于一次。
实现的责任:实现必须在<init>表达式求值期间检测<variable>或任何在<bindings>跟随它的任何绑定的<variable>的引用。如果实现检测到这样一个限制的违反,它必须抛出一个条件类型是&assertion的异常。实现可以也可以不检测每个<init>的继续是否被调用多于一次。可是,如果实现检测到的话,它必须抛出一个条件类型是&assertion的异常。
(let-values <mv-bindings> <body>) 语法
语法:<Mv-bindings>必须有形式
\(\texttt{((<formals$_1$> <init$_1$>) ...)}\),
其中,每一个<init>是一个表达式,且<body>如第11.3小节描述。在<formals>集合中任何变量必须不能出现超过一次。
语义:<Init>在当前的环境中被求值(以一些未定义的顺序),且出现在<formals>中的变量被绑定到包含<init>返回值的新鲜位置,其中<formals>匹配返回值就像lambda表达式中的<formals>在过程调用中匹配参数一样。然后,<body>在扩展后的环境中被求值,且<body>中最后一个表达式的值被返回。每个变量绑定将<body>作为它的作用域。如果<formals>不匹配的话,那么一个条件类型是&assertion的异常被抛出。
(let-values (((a b) (values 1 2))
((c d) (values 3 4)))
(list a b c d)) ⇒ (1 2 3 4)
(let-values (((a b . c) (values 1 2 3 4)))
(list a b c)) ⇒ (1 2 (3 4))
(let ((a 'a) (b 'b) (x 'x) (y 'y))
(let-values (((a b) (values x y))
((x y) (values a b)))
(list a b x y))) ⇒ (x y a b)
(let*-values <mv-bindings> <body>) 语法
语法:<Mv-bindings>必须有形式
\(\texttt{((<formals$_1$> <init$_1$>) ...)}\),
其中,每一个<init>是一个表达式,且<body>如第11.3小节描述。在每个<formals>中,任何变量必须不能出现超过一次。
语义:let*-value形式类似于let-value,但是<init>被计算和绑定被创建是按从左到右的顺序进行的,每个<formals>绑定的作用域除了<body>还包括它的右边。因此,第二个<init>在第一个<formals>可见且被初始化的环境中被求值,以此类推。
(let ((a 'a) (b 'b) (x 'x) (y 'y))
(let*-values (((a b) (values x y))
((x y) (values a b)))
(list a b x y))) ⇒ (x y x y)
注意:通过let-values表达式绑定的变量必须是不一样的,而通过let*-values表达式的不同<formals>绑定的变量则没有这个限制。
11.4.7. 顺序结构(Sequencing)
(begin <form> ...) |
语法 |
(begin <expression> <expression> ...) |
语法 |
<Begin>关键词有两个不同的作用,取决于它的上下文:
-
它可以作为一个形式出现在一个
<body>中(见第11.3小节),一个<library body>中(见第7.1小节),或一个<top-level body>(见第3章),或直接嵌套在一个内部的begin形式中。在这种情况下,begin形式必须有第一个标题行指定的形状。begin的这种用法作为一种拼接(splicing)形式—<body>里面的形式被拼接到内部周围,就好像原来的begin包装不存在一样。<Body>或<library body>中的begin形式必须是非空的,如果它在内部出现在第一个<expression>之后。 -
它可以作为一个普通的表达式出现,且必须有第二个标题行指定的形状。在这种情况下,
<expression>被按从左到右的顺序求值,且最后一个<expression>的值被返回。这种表达式类型被用作按顺序排列副作用,如赋值或输入输出。
(define x 0)
(begin (set! x 5)
(+ x 1)) ⇒ 6
(begin (display "4 plus 1 equals ")
(display (+ 4 1))) ⇒ unspecified
并打印 4 plus 1 equals 5
11.5 等价谓词
一个谓词是一个总是返回布尔值(#t或#f)的过程。一个等价谓词在计算上模拟数学上的等价关系(它是对称的(symmetric),自反的(reflexive),且传递的(transitive))。在本节表述的等价谓词中,eq?是最好或最精细的,equal?是最粗糙的。eqv?比eq?的辨别能力稍差。
\(\texttt{(eqv? obj$_1$ obj$_2$)}\) 过程
eqv?定义在对象上定义一个有用的等价关系。简单地说,它返回#t,如果obj\(_1\)和obj\(_2\)在通常情况下被认为是相等的对象的话,否则返回#f。这种关系不太好解释,但下面所列的eqv?部分规范必须被所有的实现遵守。
eqv?过程在以下情况返回#t:
- Obj\(_1\)和obj\(_2\)都是布尔,且根据
boolean=?过程它们是一样的(第11.8小节)。 - Obj\(_1\)和obj\(_2\)都是符号,且根据
symbol=?过程它们是一样的(第11.10小节)。 - Obj\(_1\)和obj\(_2\)都是精确数,且在数学上相等(见
=,第11.7小节)。 - Obj\(_1\)和obj\(_2\)都是非精确数字对象,且在数学上相等(见
=,第11.7小节),且作为参数传递给任意其它可以通过Scheme标准算术过程定义的过程时产生的结果都是一样的(从eqv?的意义上说),只要计算过程中不涉及非数。(本句已根据勘误表补全。) - Obj\(_1\)和obj\(_2\)都是字符,且根据
char=?过程它们是一样的(见第11.11小节)。 - Obj\(_1\)和obj\(_2\)都是空表。
- Obj\(_1\)和obj\(_2\)都是对象,比如点对,向量,字节向量(库的第2章),字符串,记录(库的第6章),端口(库的第8.2小节),或哈希表(库的第13章),且指向相同的存储位置(第5.10小节)。(原文中有一个重复的“哈希表”,已被删除。)
- Obj\(_1\)和obj\(_2\)都是记录类型(record-type)描述符,它们在库的第6.3小节被指定为是
eqv?等价的。
eqv?过程在以下情况返回#f:
- Obj\(_1\)和obj\(_2\)是不同的类型(第11.1小节)。
- Obj\(_1\)和obj\(_2\)是布尔,但
boolean=?过程返回#f。 - Obj\(_1\)和obj\(_2\)是符号,但
symbol=?过程返回#f。 - Obj\(_1\)和obj\(_2\)一个是精确数字对象,另一个是非精确数字对象。
- Obj\(_1\)和obj\(_2\)是有理数字对象,但
=过程返回#f。 - Obj\(_1\)和obj\(_2\)作为参数传递给任意其它可以通过Scheme标准算术过程定义的过程时产生不一样的结果(从
eqv?的意义上说),只要计算过程中不涉及非数。(本句已根据勘误表补全。) - Obj\(_1\)和obj\(_2\)是字符,但
char=?过程返回#f。 - Obj\(_1\)和obj\(_2\)一个是空表,但另一个不是。
- Obj\(_1\)和obj\(_2\)是对象,比如点对,向量,字节向量(库的第2章),字符串,记录(库的第6章),端口(库的第8.2小节),或哈希表(库的第13章),但指向不同的位置。
- Obj\(_1\)和obj\(_2\)是点对,向量,字符串,或记录,或哈希表,向其内部应用相同的访问器(也就是
car,cdr,vector-ref,string-ref, 或记录访问器)但产生的值在eqv?下返回#f。 - Obj\(_1\)和obj\(_2\)是过程,但对于某些参数有不同的行为(返回不同的值或有不同的副作用)。
注意:当obj\(_1\)和obj\(_2\)是数字对象时,`eqv?`过程返回`#t`并不意味着`=`在相同的obj\(_1\)和obj\(_2\)参数下也返回`#t`。
(eqv? 'a 'a) ⇒ #t
(eqv? 'a 'b) ⇒ #f
(eqv? 2 2) ⇒ #t
(eqv? '() '()) ⇒ #t
(eqv? 100000000 100000000) ⇒ #t
(eqv? (cons 1 2) (cons 1 2)) ⇒ #f
(eqv? (lambda () 1)
(lambda () 2)) ⇒ #f
(eqv? #f 'nil) ⇒ #f
下面的例子展示了上面的规则没有完全定义的eqv?的行为。对于这些情况,我们只能说eqv?的返回值必须是一个布尔。
(let ((p (lambda (x) x)))
(eqv? p p)) ⇒ unspecified
(eqv? "" "") ⇒ unspecified
(eqv? '#() '#()) ⇒ unspecified
(eqv? (lambda (x) x)
(lambda (x) x)) ⇒ unspecified
(eqv? (lambda (x) x)
(lambda (y) y)) ⇒ unspecified
(eqv? +nan.0 +nan.0) ⇒ unspecified
下面的例子集合展示了对有本地状态的过程使用eqv?的情况。对gen-counter的每一次调用都必须付汇一个不同的过程,因为每个过程都有它自己内部的计数。gen-loser的调用返回的过程被调用的时候行为是一样的。然而,eqv?可以不检测这种相等。
(define gen-counter
(lambda ()
(let ((n 0))
(lambda () (set! n (+ n 1)) n))))
(let ((g (gen-counter)))
(eqv? g g)) ⇒ unspecified
(eqv? (gen-counter) (gen-counter))
⇒ #f
(define gen-loser
(lambda ()
(let ((n 0))
(lambda () (set! n (+ n 1)) 27))))
(let ((g (gen-loser)))
(eqv? g g)) ⇒ unspecified
(eqv? (gen-loser) (gen-loser))
⇒ unspecified
(letrec ((f (lambda () (if (eqv? f g) 'both 'f)))
(g (lambda () (if (eqv? f g) 'both 'g))))
(eqv? f g)) ⇒ unspecified
(letrec ((f (lambda () (if (eqv? f g) 'f 'both)))
(g (lambda () (if (eqv? f g) 'g 'both))))
(eqv? f g)) ⇒ #f
实现可以在适当的时候在常量间共享结构。(根据勘误表,此处省略一句话。)因此,作用在常量上的eqv?的返回值有时是实现定义的。
(eqv? '(a) '(a)) ⇒ unspecified
(eqv? "a" "a") ⇒ unspecified
(eqv? '(b) (cdr '(a b))) ⇒ unspecified
(let ((x '(a)))
(eqv? x x)) ⇒ #t
\(\texttt{(eq? obj$_1$ obj$_2$)}\) 过程
eq?谓词和eqv?类似,除了在一些情况其识别差别的能力比eqv?更加精细之外。
eq?和eqv?确保在符号,布尔,空表,点对,过程,非空字符串,字节向量,和向量,以及记录的行为是一样的。eq?在数字对象,字符上的行为是实现定义的,但是它总是或者返回#t或者返回#f,且只有当eqv?返回#t时它才有可能返回#t。eq?谓词在空表,空向量,空字节向量和空字符串上的行为也可以是不一样的。
(eq? 'a 'a) ⇒ #t
(eq? '(a) '(a)) ⇒ unspecified
(eq? (list 'a) (list 'a)) ⇒ #f
(eq? "a" "a") ⇒ unspecified
(eq? "" "") ⇒ unspecified
(eq? '() '()) ⇒ #t
(eq? 2 2) ⇒ unspecified
(eq? #\A #\A) ⇒ unspecified
(eq? car car) ⇒ unspecified(本条已根据勘误表修改)
(let ((n (+ 2 3)))
(eq? n n)) ⇒ unspecified
(let ((x '(a)))
(eq? x x)) ⇒ #t
(let ((x '#()))
(eq? x x)) ⇒ unspecified
(let ((p (lambda (x) x)))
(eq? p p)) ⇒ unspecified
\(\texttt{(equal? obj$_1$ obj$_2$)}\) 过程
equal?谓词返回#t当且仅当它的参数(可能无限地)到正则树(regular trees)的展开作为有序树是一样的(ordered trees)。
equal?谓词对待点对和向量作为有出边(outgoing edges)的节点,使用string=?比较字符串,使用bytevector=?比较字节向量(见库的第2章),且使用eqv?比较其它节点。
(equal? 'a 'a) ⇒ #t
(equal? '(a) '(a)) ⇒ #t
(equal? '(a (b) c)
'(a (b) c)) ⇒ #t
(equal? "abc" "abc") ⇒ #t
(equal? 2 2) ⇒ #t
(equal? (make-vector 5 'a)
(make-vector 5 'a)) ⇒ #t
(equal? '#vu8(1 2 3 4 5)
(u8-list->bytevector
'(1 2 3 4 5)) ⇒ #t
(equal? (lambda (x) x)
(lambda (y) y)) ⇒ unspecified
(let* ((x (list 'a))
(y (list 'a))
(z (list x y)))
(list (equal? z (list y x))
(equal? z (list x x))))
⇒ (#t #t)
注意:equal?必须总是可以终止的,哪怕它的参数存在循环。
11.6. 过程谓词
(procedure? obj) 过程
返回#t如果obj是一个过程,否则返回#f。
(procedure? car) ⇒ #t
(procedure? 'car) ⇒ #f
(procedure? (lambda (x) (* x x)))
⇒ #t
(procedure? '(lambda (x) (* x x)))
⇒ #f
11.7. 算术(Arithmetic)
这里描述的过程实现了在第3章描述的数值塔上通用的算术。本节描述的通用过程既接受精确数也接受非精确数对象作为其参数,并根据它们参数的数值子类型执行强制转换和选取适当的操作。
库的第11章描述了定义其它数值过程的库。
11.7.1. 精确性和非精确性的传播
下面列出的过程在传递给它们的参数都是精确的时候必须返回数学上正确的精确结果:
+ - *
max min abs
numerator denominator gcd
lcm floor ceiling
truncate round rationalize
real-part imag-part make-rectangular
下面列出的过程当传递给它们的参数都是精确的且没有除数是零的时候必须返回正确的精确结果:
/
div mod div-and-mod
div0 mod0 div0-and-mod0
此外,过程expt必须返回正确的精确结果,当传递给它的第一个参数是一个精确的实数对象且第二个参数是一个精确的整数对象。
通用的规则是,一般操作返回正确精确的结果,当所有传递给它们的参数都是精确的且结果是数学上明确定义的,但是当任何一个参数是非精确的时候返回一个非精确结果。这条规则的例外包括sqrt, exp, log, sin, cos, tan, asin, acos, atan, expt, make-polar, magnitude, 和angle,其甚至可以(但不要求)在传递精确参数的时候返回非精确的结果,如这些过程的规范所示。
上面规则的一个普遍的例外是,一个实现可以返回一个精确结果尽管其参数是非精确的,但这个精确的结果对于所有可能的对这个非精确参数的精确替代,都应该是正确的。一个例子是(* 1.0 0),其可以返回0(精确地)或0.0(非精确的)。
11.7.2. 无穷大和非数的表示性(Representability)
数值操作的规范被书写就好像无穷大和非数是可以表示的,且指定许多操作,这些操作和数字对象相关,其在方法上和IEEE-754的二进制浮点算术标准相一致。Scheme的一个实现可以也可以不表示无穷大和非数;可是,一个实现必须抛出一个可继续的条件类型是&no-infinities或&no-nans(分别地见库的第11.3小节)的异常,在任何不能表示一个按规范说明的无穷大或非数的时候。在这种情况下,异常处理程序的继续是一个可以接受无穷大或非数的继续。这个要求也适用于数字对象和外部表示之间的转换,包括读取程序源代码。
11.7.3 常用操作的语义
一些操作是几个算术过程的语义基础。本节描述的这些操作的行为用作以后的参考。
11.7.3.1. 整数除法
Scheme执行整数除法的操作依赖于数学操作div, mod, div\(_0\), 和mod\(_0\),它们定义如下:
div, mod, div\(_0\), 和mod\(_0\)每个都接受两个实数x\(_1\)和x\(_2\)做为操作数,其中x\(_2\)必须是非零。
div返回一个整数,mod返回一个实数。它们的结果规定如下:
\[\begin{eqnarray*} x_1~\mathrm{div}~x_2 &=& n_d\\ x_1~\mathrm{mod}~x_2 &=& x_m \end{eqnarray*}\]其中
\[\begin{array}{c} x_1 = n_d \cdot x_2 + x_m\\ 0 \leq x_m < |x_2| \end{array}\]例子:
\[\begin{eqnarray*} 123~\mathrm{div}~10 &=& 12\\ 123~\mathrm{mod}~10 &=& 3\\ 123~\mathrm{div}~\textrm{$-10$} &=& -12\\ 123~\mathrm{mod}~\textrm{$-10$} &=& 3\\ -123~\mathrm{div}~10 &=& -13\\ -123~\mathrm{mod}~10 &=& 7\\ -123~\mathrm{div}~\textrm{$-10$} &=& 13\\ -123~\mathrm{mod}~\textrm{$-10$} &=& 7 \end{eqnarray*}\]div\(_0\)和mod\(_0\)与div和mod类似,除了mod\(_0\)的结果在一个以零为中点的半开的区间中。它们的结果规定如下:
\[\begin{eqnarray*} x_1~\mathrm{div}_0~x_2 &=& n_d\\ x_1~\mathrm{mod}_0~x_2 &=& x_m \end{eqnarray*}\]其中
\[\begin{array}{c} x_1 = n_d \cdot x_2 + x_m\\ -|\frac{x_2}{2}| \leq x_m < |\frac{x_2}{2}| \end{array}\]例子:
\[\begin{eqnarray*} 123~\mathrm{div}_0~10 &=& 12\\ 123~\mathrm{mod}_0~10 &=& 3\\ 123~\mathrm{div}_0~\textrm{$-10$} &=& -12\\ 123~\mathrm{mod}_0~\textrm{$-10$} &=& 3\\ -123~\mathrm{div}_0~10 &=& -12\\ -123~\mathrm{mod}_0~10 &=& -3\\ -123~\mathrm{div}_0~\textrm{$-10$} &=& 12\\ -123~\mathrm{mod}_0~\textrm{$-10$} &=& -3 \end{eqnarray*}\]11.7.3.2. 超越函数(Transcendental functions)
通常,超越函数\(\log\), \(\sin^{-1}\)
(arcsine), \(\cos^{-1}\) (arccosine), 和\(\tan^{-1}\)是多重定义的。\(\log z\)被定义为虚部的值域是\(-\pi\)(包括,如果区分-0.0的话,否则不包括)到\(\pi\)(包括)。\(\log 0\)是未定义的。
对于非实数的\(z\),\(\log z\)根据实数上的log定义如下
其中\(\mathrm{angle}~z\)是\(z = a\cdot e^{ib}\)按如下方式指定的角
其中,\(-\pi \leq \mathrm{angle}~z\leq \pi\),且对某些整数\(n\),\(\mathrm{angle}~z = b+2\pi n\)。
在一个参数log定义版本的基础上,两参数版本\(\log\), \(\sin^{-1} z\), \(\cos^{-1} z\),
\(\tan^{-1} z\), 和两参数版本\(\tan^{-1}\)的值可以根据下列公式定义:
\(\tan^{-1} x~y\)的值域如下表所示。星号(*)指示这个条目适用于区别负零的实现。
11.7.4. 数值操作
11.7.4.1. 数值类型谓词
(number? obj) |
过程 |
(complex? obj) |
过程 |
(real? obj) |
过程 |
(rational? obj) |
过程 |
(integer? obj) |
过程 |
这些数值类型谓词可以被应用于任何类型的参数。如果对象是命名类型的数字对象,它们返回#t,否则返回#f。通常,如果一个类型谓词对一个数字对象返回真,那么,所有更高的类型谓词对那些数字对象也返回真。因此,如果一个类型谓词对一个数字对象返回假,那么,所有更低的类型谓词对那些数字对象也返回假。
如果z是一个复数对象,那么(real? z)为真,当且仅当(zero? (imag-part z))和(exact? (imag-part z))同时为真。
如果x是一个实数对象,那么(rational? x)为真,当且仅当存在精确的整数对象k\(_1\)和k\(_2\)使得\(\texttt{(= x (/ k$_1$ k$_2$))}\)和\(\texttt{(= (numerator x) k$_1$)}\)和\(\texttt{(= (denominator x) k$_2$)}\)都为真。因此,无限大和非数不是有理数对象。
如果q是一个有理数字对象,那么,(integer? q)为真,当且仅当如果(= (denominator q) 1)为真。如果q不是一个有利数字对象,那么(integer? q)是#f。
(complex? 3+4i) ⇒ #t
(complex? 3) ⇒ #t
(real? 3) ⇒ #t
(real? -2.5+0.0i) ⇒ #f
(real? -2.5+0i) ⇒ #t
(real? -2.5) ⇒ #t
(real? #e1e10) ⇒ #t
(rational? 6/10) ⇒ #t
(rational? 6/3) ⇒ #t
(rational? 2) ⇒ #t
(integer? 3+0i) ⇒ #t
(integer? 3.0) ⇒ #t
(integer? 8/4) ⇒ #t
(number? +nan.0) ⇒ #t
(complex? +nan.0) ⇒ #t
(real? +nan.0) ⇒ #t
(rational? +nan.0) ⇒ #f
(complex? +inf.0) ⇒ #t
(real? -inf.0) ⇒ #t
(rational? -inf.0) ⇒ #f
(integer? -inf.0) ⇒ #f
注意:除了number?之外,这些类型谓词的行为在非精确数字对象上是不可靠的,因为任何不精确都可以影响结果。
(real-valued? obj) |
过程 |
(rational-valued? obj) |
过程 |
(integer-valued? obj) |
过程 |
这些数值类型谓词可被应用于任何类型的参数。real-valued?过程返回#t,如果这个对象是一个数字对象且在=的意义上等于某个实数对象,或如果这个对象是一个非数,或一个实部是非数虚部在zero?的意义上是零的复数对象。rational-valued?和integer-valued?过程返回#t,如果这个对象是一个数字对象,且在=的意义下等于名字中的类型的某些对象,否则返回#f。
(real-valued? +nan.0) ⇒ #t
(real-valued? +nan.0+0i) ⇒ #t
(real-valued? -inf.0) ⇒ #t
(real-valued? 3) ⇒ #t
(real-valued? -2.5+0.0i) ⇒ #t
(real-valued? -2.5+0i) ⇒ #t
(real-valued? -2.5) ⇒ #t
(real-valued? #e1e10) ⇒ #t
(rational-valued? +nan.0) ⇒ #f
(rational-valued? -inf.0) ⇒ #f
(rational-valued? 6/10) ⇒ #t
(rational-valued? 6/10+0.0i) ⇒ #t
(rational-valued? 6/10+0i) ⇒ #t
(rational-valued? 6/3) ⇒ #t
(integer-valued? 3+0i) ⇒ #t
(integer-valued? 3+0.0i) ⇒ #t
(integer-valued? 3.0) ⇒ #t
(integer-valued? 3.0+0.0i) ⇒ #t
(integer-valued? 8/4) ⇒ #t
注意:这些过程测试一个给定的数字对象在住损失数值精度的情况下是否可以被正确地转换为指定的类型。特别地,这些谓词的行为和real?, rational?, 和integer?在虚部是非精确的零的复数对象上的行为是不一样的。
注意:这些类型谓词在非精确数字对象上的行为是不可靠的,这是因为任何的非精确可能影响结果。
(exact? z) |
过程 |
(inexact? z) |
过程 |
这些数值谓词给出了量的精确性的测试。对任意的数字对象,有且只有一个谓词的值为真。
(exact? 5) ⇒ #t
(inexact? +inf.0) ⇒ #t
11.7.4.2. 通用转换
(inexact z) |
过程 |
(exact z) |
过程 |
inexact过程返回z的非精确表示。如果适当类型的非精确数字对象被绑定了精度,那么,返回值是一个接近参数的非精确数字对象。如果一个精确参数没有合理地接近非精确等价物,那么一个条件类型是&implementation-restriction(此异常类型已根据勘误表修改)的异常可以被抛出。
注意:对于一个量级有限的实数对象,如果它大到以至于没有合理的作为一个非精确数的合理的近似,一个合理的非精确等价物可以是+inf.0或-inf.0。类似地,一个各部分都是有限的复数对象的非精确表示可以有无限的部分。
exact过程返回z的一个精确表示。返回值是数值上最接近于参数的精确数字对象;在大部分情况下,这个过程的结果应当在数值上等于它的参数。如果一个非精确参数没有合理的精确等价物,那么一个条件类型是&implementation-restriction(此异常类型已根据勘误表修改)的异常可以被抛出。
这些过程在一个实现定义的范围中实现了精确和非精确整数对象的自然的一一对应。
inexact和exact过程是幂等(idempotent)的。
11.7.4.3. 算术操作
\(\texttt{(= z$_1$ z$_2$ z$_3$ ...)}\) |
过程 |
\(\texttt{(< x$_1$ x$_2$ x$_3$ ...)}\) |
过程 |
\(\texttt{(> x$_1$ x$_2$ x$_3$ ...)}\) |
过程 |
\(\texttt{(<= x$_1$ x$_2$ x$_3$ ...)}\) |
过程 |
\(\texttt{(>= x$_1$ x$_2$ x$_3$ ...)}\) |
过程 |
这些过程返回#t,如果它们的参数(分别地):相等,单调递增,单调递减,单调非递减,单调非递增,其它情况返回#f。
(= +inf.0 +inf.0) ⇒ #t
(= -inf.0 +inf.0) ⇒ #f
(= -inf.0 -inf.0) ⇒ #t
对于任何的既不是无穷大也不是非数的实数对象x(以下条目已根据勘误表修改):
(< -inf.0 x +inf.0)) ⇒ #t
(> +inf.0 x -inf.0)) ⇒ #t
对任何数字对象z:
(= +nan.0 z) ⇒ #f
对任何实数对象x,
(< +nan.0 x) ⇒ #f
(> +nan.0 x) ⇒ #f
这些谓词必须是传递的。
注意:这些谓词在类Lisp语言的传统实现中不是传递的。
注意:尽管用这些谓词比较非精确数字对象是可以的,但是,结果可能是不可靠的,这时因为很小的不精确都可能影响结果;对`=`和`zero?`(下面)尤其是这样。
如果有疑问的话,请咨询数值分析师。
(zero? z) |
过程 |
(positive? x) |
过程 |
(negative? x) |
过程 |
(odd? n) |
过程 |
(even? n) |
过程 |
(finite? x) |
过程 |
(infinite? x) |
过程 |
(nan? x) |
过程 |
这些数值谓词测试一个数字对象的一个特定的属性,返回#t或#f。zero?过程测试数字对象是否=零,positive?测试是否大于零,negative?测试是否小于零,odd?测试是否是奇数,even?测试是否是偶数,finit?测试是否既不是无穷大也不是一个非数,infinite?测试是否是一个无穷大,nan?测试是否是一个非数。
(zero? +0.0) ⇒ #t
(zero? -0.0) ⇒ #t
(zero? +nan.0) ⇒ #f
(positive? +inf.0) ⇒ #t
(negative? -inf.0) ⇒ #t
(positive? +nan.0) ⇒ #f
(negative? +nan.0) ⇒ #f
(finite? +inf.0) ⇒ #f
(finite? 5) ⇒ #t
(finite? 5.0) ⇒ #t
(infinite? 5.0) ⇒ #f
(infinite? +inf.0) ⇒ #t
注意:就上面的谓词而言,结果可能是不可靠的,这时因为一个晓得不精确都可以影响结果。
\(\texttt{(max x$_1$ x$_2$ ...)}\) |
过程 |
\(\texttt{(min x$_1$ x$_2$ ...)}\) |
过程 |
这些过程返回它们参数的最大值和最小值。
(max 3 4) ⇒ 4
(max 3.9 4) ⇒ 4.0
对于任何不是非数的实数对象x(本句已根据勘误表修改):
(max +inf.0 x) ⇒ +inf.0
(min -inf.0 x) ⇒ -inf.0
注意:如果每个参数都是非精确的,那么结果也是非精确的(除非过程可以证明误差没有大到可以影响结果,这只有在不寻常的实现中才有可能)。如果min和max被用作比较混合有不同精确性的对象,且结果的数值值也不能在不丢失准确的情况下被表示成一个非精确数,那么这个过程抛出一个条件类型是&implementation-restriction的异常。
\(\texttt{(+ z$_1$ ...)}\) |
过程 |
\(\texttt{(* z$_1$ ...)}\) |
过程 |
这些过程返回它们的参数的和和积。
(+ 3 4) ⇒ 7
(+ 3) ⇒ 3
(+) ⇒ 0
(+ +inf.0 +inf.0) ⇒ +inf.0
(+ +inf.0 -inf.0) ⇒ +nan.0
(* 4) ⇒ 4
(*) ⇒ 1
(* 5 +inf.0) ⇒ +inf.0
(* -5 +inf.0) ⇒ -inf.0
(* +inf.0 +inf.0) ⇒ +inf.0
(* +inf.0 -inf.0) ⇒ -inf.0
(* 0 +inf.0) ⇒ 0 or +nan.0
(* 0 +nan.0) ⇒ 0 or +nan.0
(* 1.0 0) ⇒ 0 or 0.0
对任何既不是无穷大也不是非数的实数对象x:
(+ +inf.0 x) ⇒ +inf.0
(+ -inf.0 x) ⇒ -inf.0
对任何实数对象x:
(+ +nan.0 x) ⇒ +nan.0
对任何不是精确的0的x:
(* +nan.0 x) ⇒ +nan.0
如果这些过程中任何一个被用于混合了非有理实数和非实数复数参数,它们或者抛出一个条件类型是&implementation-restriction的异常或者返回一个未定义的数字对象。
区别-0.0的实现必须接受和下面例子一致行为:
(+ 0.0 -0.0) ⇒ 0.0
(+ -0.0 0.0) ⇒ 0.0
(+ 0.0 0.0) ⇒ 0.0
(+ -0.0 -0.0) ⇒ -0.0
\(\texttt{(- z)}\) |
过程 |
\(\texttt{(- z$_1$ z$_2$ z$_3$ ...)}\) |
过程(已根据勘误表修改) |
有两个或多个参数时,这个过程以左结合的方式返回它参数的差。有一个参数时,它返回它参数的加法逆元。
(- 3 4) ⇒ -1
(- 3 4 5) ⇒ -6
(- 3) ⇒ -3
(- +inf.0 +inf.0) ⇒ +nan.0
如果这个过程被用于混合了非有理实数和非实数复数参数,它们或者抛出一个条件类型是&implementation-restriction的异常或者返回一个未定义的数字对象。
区别-0.0的实现必须接受和下面例子一致行为:
(- 0.0) ⇒ -0.0
(- -0.0) ⇒ 0.0
(- 0.0 -0.0) ⇒ 0.0
(- -0.0 0.0) ⇒ -0.0
(- 0.0 0.0) ⇒ 0.0
(- -0.0 -0.0) ⇒ 0.0
\(\texttt{(/ z)}\) |
过程 |
\(\texttt{(/ z$_1$ z$_2$ z$_3$ ...)}\) |
过程(已根据勘误表修改) |
如果所有的参数都是精确的,那么除数必须是非零。有两个或多个参数时,这个过程以左结合的方式返回它参数的商。有一个参数时,它返回它参数的除法逆元。
(/ 3 4 5) ⇒ 3/20
(/ 3) ⇒ 1/3
(/ 0.0) ⇒ +inf.0
(/ 1.0 0) ⇒ +inf.0
(/ -1 0.0) ⇒ -inf.0
(/ +inf.0) ⇒ 0.0
(/ 0 0) ⇒ &assertion异常
(/ 3 0) ⇒ &assertion异常
(/ 0 3.5) ⇒ 0.0
(/ 0 0.0) ⇒ +nan.0
(/ 0.0 0) ⇒ +nan.0
(/ 0.0 0.0) ⇒ +nan.0
如果这个过程被用于混合了非有理实数和非实数复数参数,它们或者抛出一个条件类型是&implementation-restriction的异常或者返回一个未定义的数字对象。
(abs x) 过程
返回它参数的绝对值。
(abs -7) ⇒ 7
(abs -inf.0) ⇒ +inf.0
\(\texttt{(div-and-mod x$_1$ x$_2$)}\) |
过程 |
\(\texttt{(div x$_1$ x$_2$)}\) |
过程 |
\(\texttt{(mod x$_1$ x$_2$)}\) |
过程 |
\(\texttt{(div0-and-mod0 x$_1$ x$_2$)}\) |
过程 |
\(\texttt{(div0 x$_1$ x$_2$)}\) |
过程 |
\(\texttt{(mod0 x$_1$ x$_2$)}\) |
过程 |
这些过程实现了数论整数除法,且返回对应于第11.7.3.1小节指定的数学运算的结果。如果\(x_1\)和\(x_2\)是精确的,\(x_2\)必须是非零。在这些过程实现了数论整数除法,且返回对应于第11.7.3.1小节中的数学要求不能被任何数字对象满足的情况下,或者一个条件类型是&implementation-restriction的异常被抛出,或者未定义的数字对象(对于div,mod, div0和mod0来说是一个,对于div-and-mod和div0-and-mod0来说是两个)被返回(本段已根据勘误表修改)。
(div \(x_1\) \(x_2\)) ⇒ \(x_1\) div \(x_2\)
(mod \(x_1\) \(x_2\)) ⇒ \(x_1\) mod \(x_2\)
(div-and-mod \(x_1\) \(x_2\)) ⇒ \(x_1\) div \(x_2\), \(x_1\) mod \(x_2\)
; 两个返回值
(div0 \(x_1\) \(x_2\)) ⇒ \(x_1\) div0 \(x_2\)
(mod0 \(x_1\) \(x_2\)) ⇒ \(x_1\) mod0 \(x_2\)
(div0-and-mod0 \(x_1\) \(x_2\))
⇒ \(x_1\) div0 \(x_2\), \(x_1\) mod0 \(x_2\)
; 两个返回值
\(\texttt{(gcd n$_1$ ...)}\) |
过程 |
\(\texttt{(lcm n$_1$ ...)}\) |
过程 |
这些过程返回它们参数的最大公因数或最小公倍数。结果总是非负的。
(gcd 32 -36) ⇒ 4
(gcd) ⇒ 0
(lcm 32 -36) ⇒ 288
(lcm 32.0 -36) ⇒ 288.0
(lcm) ⇒ 1
(numerator q) |
过程 |
(denominator q) |
过程 |
这些过程返回它们参数的分子或分母;结果根据其参数的最简分数计算。分母总是正的。0的分母被定义为1。
(numerator (/ 6 4)) ⇒ 3
(denominator (/ 6 4)) ⇒ 2
(denominator
(inexact (/ 6 4))) ⇒ 2.0
(floor x) |
过程 |
(ceiling x) |
过程 |
(truncate x) |
过程 |
(round x) |
过程 |
对于不是无穷大或非零的非精确参数,这些过程返回非精确的整数对象。对于这些参数,floor过程返回不大于x的最大整数对象。ceiling过程返回不小于x的最小整数对象。truncate过程返回绝对值不大于x绝对值的最接近x的整数对象。round过程返回最接近x的整数对象,当x正好在两个整数的中间位置时舍入到偶数。
注意:如果这些过程中的一个参数是非精确的,那么结果也是非精确的。如果需要一个精确的值,结果应该被传递给`exact`过程。
尽管无穷大和非数不是整数对象,这些过程会返回一个无穷大当给一个无穷大参数时,返回一个非数当给一个非数时。
(floor -4.3) ⇒ -5.0
(ceiling -4.3) ⇒ -4.0
(truncate -4.3) ⇒ -4.0
(round -4.3) ⇒ -4.0
(floor 3.5) ⇒ 3.0
(ceiling 3.5) ⇒ 4.0
(truncate 3.5) ⇒ 3.0
(round 3.5) ⇒ 4.0
(round 7/2) ⇒ 4
(round 7) ⇒ 7
(floor +inf.0) ⇒ +inf.0
(ceiling -inf.0) ⇒ -inf.0
(round +nan.0) ⇒ +nan.0
\(\texttt{(rationalize x$_1$ x$_2$)}\) |
过程 |
retionalize过程返回一个与\(x_1\)和差值不大于\(x_2\)的最简有理数(simplest rational number)(本句已根据勘误表修改)。有理数\(r_1\)比有理数\(r_2\)更简单(simpler)意味着\(r_1 = p_1/q_1\),\(r_2 = p_2/q_2\)(以最简分数表示),且\(|p_1| \leq |p_2|\),且\(|q_1| \leq |q_2|\)。因此3/5比4/7更简单。尽管不是所有的有理数都可以用这种顺序比较(考虑2/7和3/5),但是任何区间都包含一个比区间中的任何其它有理数都简单的有理数(比较简单的2/5位于2/7和3/5之间)。注意0 = 0/1是所有有理数中最简单的一个。
(rationalize (exact .3) 1/10)
⇒ 1/3
(rationalize .3 1/10)
⇒ #i1/3 ; 大约
(rationalize +inf.0 3) ⇒ +inf.0
(rationalize +inf.0 +inf.0) ⇒ +nan.0
(rationalize 3 +inf.0) ⇒ 0.0
前两个例子只在非精确实数对象有足够精度时有效。
\(\texttt{(exp z)}\) |
过程 |
\(\texttt{(log z)}\) |
过程 |
\(\texttt{(log z$_1$ z$_2$)}\) |
过程 |
\(\texttt{(sin z)}\) |
过程 |
\(\texttt{(cos z)}\) |
过程 |
\(\texttt{(tan z)}\) |
过程 |
\(\texttt{(asin z)}\) |
过程 |
\(\texttt{(acos z)}\) |
过程 |
\(\texttt{(atan z)}\) |
过程 |
\(\texttt{(atan x$_1$ x$_2$)}\) |
过程 |
这些过程计算通常的超越函数。exp过程计算以e为基数的z的指数。有一个参数的log过程计算z的自然对数(不是以十为基数的对数);\(\texttt{(log $z_1$ $z_2$)}\)计算以\(z_2\)为基数的\(z_1\)的对数。asin,acos和atan过程分别计算反正弦,反余弦和反正切。两个参数版本的atan计算\(\texttt{(angle (make-rectangular $x_2$ $x_1$))}\)。
基本的数学运算见第11.7.3.2小节。当传递非精确数作为参数的时候这些过程可以返回非精确的结果。
(exp +inf.0) ⇒ +inf.0
(exp -inf.0) ⇒ 0.0
(log +inf.0) ⇒ +inf.0
(log 0.0) ⇒ -inf.0
(log 0) ⇒ &assertion异常
(log -inf.0)
⇒ +inf.0+3.141592653589793i
; 大约
(atan -inf.0)
⇒ -1.5707963267948965 ; 大约
(atan +inf.0)
⇒ 1.5707963267948965 ; 大约
(log -1.0+0.0i)
⇒ 0.0+3.141592653589793i ; 大约
(log -1.0-0.0i)
⇒ 0.0-3.141592653589793i ; 大约
; 如果区别-0.0的话
(sqrt z) 过程
返回z的主平方根。对于实数z,结果或者是正的实部,或者是零实部和非负的虚部。根据第11.7.3.2小节定义的log,(sqrt z)的值可以被表示成\(e^{\frac{\log z}{2}}\)。
当传递的参数是一个精确的参数时,sqrt过程可以返回一个非精确的结果。
(sqrt -5)
⇒ 0.0+2.23606797749979i ; 大约
(sqrt +inf.0) ⇒ +inf.0
(sqrt -inf.0) ⇒ +inf.0i
(exact-integer-sqrt k) 过程
exact-integer-sqrt过程返回两个非负精确整数对象s和r,其中\(k = s^2 + r\)且\(k < (s+1)^2\)。
(exact-integer-sqrt 4) ⇒ 2 0
; 两个返回值
(exact-integer-sqrt 5) ⇒ 2 1
; 两个返回值
\(\texttt{(expt $z_1$ $z_2$)}\) 过程
返回\(z_1\)的\(z_2\)次幂。对于非零的\(z_1\),是\(e^{z_2 \log z_1}\)。\(0.0^{z}\)是1.0如果z = 0.0,或0.0如果(real-part z)是正的。在其它第一个参数是零的情况下,或者抛出一个条件类型是&implementation-restriction的异常,或者一个未定义的数字对象呗返回。
对一个精确的实数对象\(z_1\)和一个精确的整数对象\(z_2\),\(\texttt{(expt $z_1$ $z_2$)}\)必须返回一个精确的结果。对于其它所有的\(z_1\)和\(z_2\),\(\texttt{(expt $z_1$ $z_2$)}\)可以返回一个非精确的结果,甚至当\(z_1\)和\(z_2\)都是精确的时候。
(expt 5 3) ⇒ 125
(expt 5 -3) ⇒ 1/125
(expt 5 0) ⇒ 1
(expt 0 5) ⇒ 0
(expt 0 5+.0000312i) ⇒ 0.0(本条已根据勘误表修改)
(expt 0 -5) ⇒ 未定义
(expt 0 -5+.0000312i) ⇒ 未定义
(expt 0 0) ⇒ 1
(expt 0.0 0.0) ⇒ 1.0
\(\texttt{(make-rectangular x$_1$ x$_2$)}\) |
过程 |
\(\texttt{(make-polar x$_3$ x$_4$)}\) |
过程 |
\(\texttt{(real-part z)}\) |
过程 |
\(\texttt{(imag-part z)}\) |
过程 |
\(\texttt{(magnitude z)}\) |
过程 |
\(\texttt{(angle z)}\) |
过程 |
假设\(a_1\),\(a_2\),\(a_3\)和\(a_4\)是实数,且c是一个复数,那么下面的式子成立:
\[c = a_1 + a_2 i = a_3 e^{i a_4}\]如果\(x_1\),\(x_2\),\(x_3\)和\(x_4\)分别是表示\(a_1\),\(a_2\),\(a_3\)和\(a_4\)的数字对象,那么,\(\texttt{(make-rectangular $x_1$ $x_2$}\)返回c,\(\texttt{(make-polar $x_3$ $x_4$)}\)也返回c。
(make-rectangular 1.1 2.2)
⇒ 1.1+2.2i ; 大约
(make-polar 1.1 2.2)
⇒ 1.1@2.2 ; 大约
对应地,如果\(-\pi \leq a_4 \leq \pi\),且如果z是一个表示c的数字对象,那么(real-part z)返回\(a_1\),(imag-part z)返回\(a_2\),(magnitude z)返回\(a_3\),(angle z)返回\(a_4\)。
(real-part 1.1+2.2i) ⇒ 1.1 ; 大约
(imag-part 1.1+2.2i) ⇒ 2.2 ; 大约(本条已根据勘误表修改)
(magnitude 1.1@2.2) ⇒ 1.1 ; 大约
(angle 1.1@2.2) ⇒ 2.2 ; 大约
(angle -1.0)
⇒ 3.141592653589793 ; 大约
(angle -1.0+0.0i)
⇒ 3.141592653589793 ; 大约
(angle -1.0-0.0i)
⇒ -3.141592653589793 ; 大约
; 如果区别-0.0的话
(angle +inf.0) ⇒ 0.0
(angle -inf.0)
⇒ 3.141592653589793 ; 大约
此外,假设\(x_1\)和\(x_2\)是这样的,\(x_1\),\(x_2\)其中一个是无穷大,那么
(make-rectangular \(x_1\) \(x_2\)) ⇒ z
(magnitude z) ⇒ +inf.0
make-polar,magnitude和angle过程可以返回非精确的结果甚至当传递精确参数的时候。
(angle -1)
⇒ 3.141592653589793 ; 大约
11.7.4.4. 数值输入和输出
(number->string z) |
过程 |
(number->string z radix) |
过程 |
(number->string z radix precision) |
过程 |
Radix(基数)必须是一个精确的整数对象,可以是2,8,10或16。如果省略的话,radix默认是10。如果一个precision(精度)被指定的话,那么z必须是一个非精确的复数对象,precision必须是一个精确的整数对象,且radix必须是10。number->string过程接受一个数字对象和一个基数且在给定的基数下返回一个作为传入的数字对象的外部表示的字符串,使得
(let ((number z) (radix radix))
(eqv? (string->number
(number->string number radix)
radix)
number))
为真。如果没有可能的结果让这个表达式为真,那么一个条件类型是&implementation-restriction的异常被抛出。
注意:错误情况只会出现在当z不是一个复数对象或是一个实部或虚部是非有理的时候。
如果一个precision被指定,那么结果的非精确实部的表示指定一个显式的<mantissa width>p,除非它们是无穷大或非数,且为了上面的表达式为真p至少是p ≥ precision。
如果z是非精确的,基数是10,且上面的表达式和条件可以被一个包含小数点的结果满足,那么这个结果包含小数点且使用能让上面表达式和条件为真的最少的数字表示2324(不包括指数,尾部的零,和位数宽度);否则结果的格式是未定义的。
number->string的结果从不包含一个显式的基数前缀。
(string->number string) |
过程 |
(string->number string radix) |
过程 |
返回string表示的最大精度的数字对象。Radix必须是一个必须是一个精确的整数对象,可以是2,8,10或16。如果提供的话,radix是可以被字符串中的显式的基数前缀(比如,"#o177")覆盖的默认基数。如果radix没有被提供,那么默认的基数是10.如果string不是一个语法上合法的数字对象的表示或者不是一个零分母的实数对象的符号,那么string->number返回#f。
(string->number "100") ⇒ 100
(string->number "100" 16) ⇒ 256
(string->number "1e2") ⇒ 100.0
(string->number "0/0") ⇒ #f
(string->number "+inf.0") ⇒ +inf.0
(string->number "-inf.0") ⇒ -inf.0
(string->number "+nan.0") ⇒ +nan.0
注意(已根据勘误表修改):如果string->number的参数是一个字符串,且指定了一个合法的基数,它必须返回一个数字对象或#f;它不可以抛出一个异常。
(也就是说,string->number可以在它的参数不是一个字符串时抛出一个异常。)
11.8. 布尔
标准布尔对象真和假又外部表示#t和#f。然而,所有的对象当中,只有#f在条件表达式中被认为是假。见第5.7小节。
注意:习惯于其它Lisp方言的程序员应当注意Scheme区分`#t`和空表,同时也区分符号`nil`。
(not obj) 过程
如果obj是#f返回#t,否则返回#f。
(not #t) ⇒ #f
(not 3) ⇒ #f
(not (list 3)) ⇒ #f
(not #f) ⇒ #t
(not '()) ⇒ #f
(not (list)) ⇒ #f
(not 'nil) ⇒ #f
(boolean? obj) 过程
如果obj是#t或f返回#t,否则返回#f。
(boolean? #f) ⇒ #t
(boolean? 0) ⇒ #f
(boolean? '()) ⇒ #f
\(\texttt{(boolean=? $bool_1$ $bool_2$ $bool_3$ ...)}\) 过程
如果所有的布尔是一样的话返回#t。
11.9. 点对和表
一个点对是一个有两个域的组合结构,这两个域叫做car和cdr域(由于历史原因)。点对通过过程cons创建。car和cdr域可以通过过程car和cdr访问。
点对主要用于表示表。表可以递归定义为:或者是一个空表,或者是一个cdr域为表的点对。更准确地说,表的集合被定义为符合以下条件的最小集合X:
- 空表在X中。
- 如果list在X中,则任何cdr域包含着list的点对也在X中。
表中连续各点对的car域内的对象是表的元素。例如,一个拥有两个元素的表是一个点对,该点对的car域包含表的第一个元素,其cdr域又是一个点对,这个点对的car域包含表的第二个元素,cdr域是空表。表的长度是元素的数量,也等于其点对的数量。
空表是一个隶属于其自身类型的特殊对象。它不是点对。它不包含任何元素,且长度为零。
注意:上述定义意味着所有表都有有限的长度,都以空表结尾。
不是以空表结尾的点对链称为*非严格表(improper list)。注意,非严格表不是表。可以使用表和点记法的组合表示非严格表:
(a b c . d)
等价于
(a . (b . (c . d)))
一个给定的点对是不是一个表取决于其cdr域存储的内容。
(pair? obj) 过程
如果obj是一个点对则返回#t,否则返回#f。
(pair? '(a . b)) ⇒ #t
(pair? '(a b c)) ⇒ #t
(pair? '()) ⇒ #f
(pair? '#(a b)) ⇒ #f
\(\texttt{(cons $obj_1$ $obj_2$)}\) 过程
返回一个新分配的点对,其car是\($obj_1$\)且cdr是\($obj_2$\)。这个点对保证(从eqv?的意义来说)和任何一个存在的对象都是不一样的。
(cons 'a '()) ⇒ (a)
(cons '(a) '(b c d)) ⇒ ((a) b c d)
(cons "a" '(b c)) ⇒ ("a" b c)
(cons 'a 3) ⇒ (a . 3)
(cons '(a b) 'c) ⇒ ((a b) . c)
(car pair) 过程
返回点对car域的内容。
(car '(a b c)) ⇒ a
(car '((a) b c d)) ⇒ (a)
(car '(1 . 2)) ⇒ 1
(car '()) ⇒ &assertion异常
(cdr pair) 过程
返回点对cdr域的内容。
(cdr '((a) b c d)) ⇒ (b c d)
(cdr '(1 . 2)) ⇒ 2
(cdr '()) ⇒ &assertion异常
(caar pair) |
过程 |
|---|---|
(cadr pair) |
过程 |
| \(\vdots\) | |
(cdddar pair) |
过程 |
(cddddr pair) |
过程 |
这些过程是car和cdr的组合。例如,caddr可定义为:
(define caddr (lambda (x) (car (cdr (cdr x)))))。
Scheme提供最高可达四层的任意组合。总共有二十八个这样的过程。
(null? obj) 过程
如果obj是一个空表则返回#t,否则返回#f。
(list? obj) 过程
如果obj是一个表则返回#t,否则返回#f。按照定义,所有的表都是长度有限且以空表结尾的点对链。
(list? '(a b c)) ⇒ #t
(list? '()) ⇒ #t
(list? '(a . b)) ⇒ #f
(list obj ...) 过程
返回新分配的它的参数的表。
(list 'a (+ 3 4) 'c) ⇒ (a 7 c)
(list) ⇒ ()
(length list) 过程
返回表的长度。
(length '(a b c)) ⇒ 3
(length '(a (b) (c d e))) ⇒ 3
(length '()) ⇒ 0
(append list ... obj) |
过程 |
(append) |
过程(根据勘误表添加) |
返回一个可能的非严格表,其包含第一个list以及跟在其后的其它list的元素,obj作为最后点对的cdr域。如果obj不是一个表,那么结果是一个非严格表。如果在没有参数的情况下调用,append过程返回空表。
(append '(x) '(y)) ⇒ (x y)
(append '(a) '(b c d)) ⇒ (a b c d)
(append '(a (b)) '((c))) ⇒ (a (b) (c))
(append '(a b) '(c . d)) ⇒ (a b c . d)
(append '() 'a) ⇒ a
(append) ⇒ ()
(append 'a) ⇒ a
如果append包含非空的点对链,它总是新分配。如果没有点对被分配,obj被返回。返回值由所有参数的新的点对组成,除了最后一个参数;最后一个参数仅仅是被放在新的结构的结尾(本句根据勘误表添加)。
(reverse list) 过程
返回一个新分配的表,其以逆序的方式包含list中的元素。
(reverse '(a b c)) ⇒ (c b a)
(reverse '(a (b c) d (e (f))))
⇒ ((e (f)) d (b c) a)
(list-tail list k) 过程
List应该是一个长度至少是k的表。list-tail过程返回list点对的子链,通过省略前k个元素获得。
(list-tail '(a b c d) 2) ⇒ (c d)
实现责任:实现必须检测list至少是长度k的点对的链。它不需要检查超过这个长度的点对的链。
(list-ref list k) 过程
List必须是一个长度至少是k + 1的表。list-ref过程返回list的第k个元素(已根据勘误表修改)。
(list-ref '(a b c d) 2) ⇒ c
实现责任:实现必须检测list至少是长度k + 1的点对的链。它不需要检查超过这个长度的点对的链。
\(\texttt{(map proc $list_1$ $list_2$ ...)}\) 过程
所有的list应该有相同的长度。Proc应该接受和list一样多的参数且返回一个单独的值。Proc不应该更改任何一个list。
map过程将proc逐个元素地应用到list的元素上且返回一个按顺序的结果的表。Proc总是在相同的map本身的动态环境中被调用。proc应用到list元素的顺序是未定义的。如果从map出现多个返回,早期返回的返回值不会被改变。
(map cadr '((a b) (d e) (g h)))
⇒ (b e h)
(map (lambda (n) (expt n n))
'(1 2 3 4 5))
⇒ (1 4 27 256 3125)
(map + '(1 2 3) '(4 5 6)) ⇒ (5 7 9)
(let ((count 0))
(map (lambda (ignored)
(set! count (+ count 1))
count)
'(a b))) ⇒ (1 2) or (2 1)
实现责任:实现应该检查所有的list有相同的长度。实现必须检查在proc上应用了上面描述的生存期的限制。一个实现可以在应用之前检查proc是不是一个合适的参数。
\(\texttt{(for-each proc $list_1$ $list_2$ ...)}\) 过程
所有的list应该有相同的长度。Proc应该接受和list一样多的参数。Proc不应该更改任何一个list。
for-each过程以副作用为目的将proc逐个元素地应用到list的元素上,以从第一个元素到最后一个元素的顺序。Proc总是在相同的for-each本身的动态环境中被调用。for-each的返回值是未定义的。
(let ((v (make-vector 5)))
(for-each (lambda (i)
(vector-set! v i (* i i)))
'(0 1 2 3 4))
v) ⇒ #(0 1 4 9 16)
(for-each (lambda (x) x) '(1 2 3 4))
⇒ 未定义
(for-each even? '()) ⇒ 未定义
实现责任:实现应该检查所有的list有相同的长度。实现必须检查在proc上应用了上面描述的生存期的限制。一个实现可以在应用之前检查proc是不是一个合适的参数。
注意:proc的实现可以也可以不在最后一个元素上对proc实行尾调用。
11.10. 符号
符号是建立在如下事实上的对象,两个符号是相同的(从eq?,eqv?和equal?的意义来说)当且仅当它们的名字以同样的方式拼写。一个符号字面量使用quote形式。
(symbol? obj) 过程
如果obj是一个符号则返回#t,否则返回#f。
(symbol? 'foo) ⇒ #t
(symbol? (car '(a b))) ⇒ #t
(symbol? "bar") ⇒ #f
(symbol? 'nil) ⇒ #t
(symbol? '()) ⇒ #f
(symbol? #f) ⇒ #f
(symbol->string symbol) 过程
以不可变字符串的形式返回symbol的名字。
(symbol->string 'flying-fish)
⇒ "flying-fish"
(symbol->string 'Martin) ⇒ "Martin"
(symbol->string
(string->symbol "Malvina"))
⇒ "Malvina"
\(\texttt{(symbol=? $symbol_1$ $symbol_2$ $symbol_3$ ...)}\)
如果符号是一样的则返回#t,也就是说如果它们的名字拼写相同的话。
(string->symbol string) 过程
返回名字是string的符号。
(eq? 'mISSISSIppi 'mississippi)
⇒ #f
(string->symbol "mISSISSIppi")
⇒the symbol with name "mISSISSIppi"
(eq? 'bitBlt (string->symbol "bitBlt"))
⇒ #t
(eq? 'JollyWog
(string->symbol
(symbol->string 'JollyWog)))
⇒ #t
(string=? "K. Harper, M.D."
(symbol->string
(string->symbol "K. Harper, M.D.")))
⇒ #t
11.11. 字符
字符是表示Unicode标量值得对象16。
注意:
Unicode在最新版本的*Unicode标量值*(在0到#x10FFF之间的整数,不包括区间#xD800到#xDFFF)和人类可读“字符”直接定义了一个标准的映射。更精确地说,Unicode区别字形(glyphs),其被印刷供人类阅读,和字符(characters),其实映射到字形的抽象入口(有时那在某种程度上对周围字符敏感)。此外,标量值的不同序列有时对应于相同的字符。标量,字符,字形之间的关系是微妙和复杂的。
尽管有这样的复杂性,大部分一个有教养的人可以称之为“字符”的东西可以被单个Unicode标量值表示(尽管一些Unicode标量值的序列可以表示那个同样的字符)。比如,罗马字母,斯拉夫字母,希伯来辅音,和大部分中文字符都在这个类别中。
Unicode标量值不包括区间#xD800到#xDFFF,其是Unicode*码位(code points)*范围的一部分。然而,这个范围内的Unicode码位,即所谓的*代理(surrogates)*,是一个UTF-16编码的人工制品,且只能出现在特定的Unicode编码中。因此,所有的字符代表码位,但是,代理码位没有字符的表示法。
(char? obj)procedure 过程
如果obj是一个字符则返回#t,否则返回#f。
(char->integer char) |
过程 |
(integer->char sv) |
过程 |
Sv必须是一个Unicode标量值,也就是说,一个属于\(\left[0, \#x\textrm{D7FF}\right] \cup \left[\#x\textrm{E000}, \#x\textrm{10FFFF}\right]\)的非负精确整数对象。
传递一个字符,char->integer以一个精确整数对象的形式返回它的Unicode标量值。对于一个Unicode标量值sv,integer->char返回与其相关的字符。
(integer->char 32) ⇒ #\space
(char->integer (integer->char 5000))
⇒ 5000
(integer->char #\xD800) ⇒ &assertion异常
\(\texttt{(char=? $char_1$ $char_2$ $char_3$ ...)}\) |
过程 |
\(\texttt{(char<? $char_1$ $char_2$ $char_3$ ...)}\) |
过程 |
\(\texttt{(char>? $char_1$ $char_2$ $char_3$ ...)}\) |
过程 |
\(\texttt{(char<=? $char_1$ $char_2$ $char_3$ ...)}\) |
过程 |
\(\texttt{(char>=? $char_1$ $char_2$ $char_3$ ...)}\) |
过程 |
这些过程根据根据字符的标量值给它们强加了一个总体的顺序。
(char<? #\z #\ß) ⇒ #t
(char<? #\z #\Z) ⇒ #f
11.12. 字符串
字符串是字符的序列。
字符串的长度是它包含的字符的数量。当字符串被创建的时候这个数被固定。一个字符串的合法索引(valid indices)是小于字符串长度的整数。一个字符串的第一个字符的索引是0,第二个是1,以此类推。
(string? obj) 过程
如果obj是一个字符串则返回#t,否则返回#f。
(make-string k) |
过程 |
(make-string k char) |
过程 |
返回一个新分配的长度是k的字符串。如果传递了char参数,那么这个字符串的所有元素被初始化为char,否则字符串的内容是未定义的。
(string char ...) 过程
返回一个新分配的由其参数组成的字符串。
(string-length string) 过程
以一个精确整数对象的形式返回给定的string中字符的数量。
(string-ref string k) 过程
K必须是string的一个合法的索引。string-ref以从零开始的索引返回string的第k个字符。
注意:实现应该让string-ref在常数时间内运行完成。
\(\texttt{(string=? $string_1$ $string_2$ $string_3$ ...)}\)
如果所有的字符串的长度都是一样的且包含相同的字符则返回#t。否则string=?过程返回#f。
(string=? "Straße" "Strasse")
⇒ #f
\(\texttt{(string<? $string_1$ $string_2$ $string_3$ ...)}\) |
过程 |
\(\texttt{(string>? $string_1$ $string_2$ $string_3$ ...)}\) |
过程 |
\(\texttt{(string<=? $string_1$ $string_2$ $string_3$ ...)}\) |
过程 |
\(\texttt{(string>=? $string_1$ $string_2$ $string_3$ ...)}\) |
过程 |
这些操作是字符上对应于顺序的字符串的词典扩展。比如,string<?是char<?在字符上的顺序在字符串上的词典序。如果两个字符串长度不同,但多达较短字符串的长度是一样的,那么较短的字符串在词典序上被认为小于较长的字符串。
(string<? "z" "ß") ⇒ #t
(string<? "z" "zz") ⇒ #t
(string<? "z" "Z") ⇒ #f
(substring string start end) 过程
String必须是一个字符串,且start和end必须是一个精确的整数对象,且符合
\[0 \leq start \leq end \leq \texttt{(string-length $string$)}\rm。\]substring过程返回一个新分配的字符串,其形成于string以索引start(包括)开始及以end结束(不包括)的字符。
(string-append string ...) 过程
返回一个新分配的字符串,其字符是给定字符串的串联。
(string->list string) |
过程 |
(list->string list) |
过程 |
List必须是一个字符的表。string->list过程返回一个新分配的字符的表,这些字符是组成给定字符串的字符。list->string过程返回一个新分配的字符串,其由list当中的字符组成。从equal?的角度来说,string->list和list->string是互逆的过程。
\(\texttt{(string-for-each proc $string_1$ $string_2$ ...)}\) 过程
所有的string必须有相同的长度。Proc应该接受和string个数相同的参数。string-for-each过程将proc逐个元素地应用到string的字符上,其以副作用为目的,并且是按照从第一个字符到最后一个的顺序。Proc总是在相同的string-for-each本身的动态环境中被调用。string-for-each的返回值是未定义的。
类似于for-each。
实现责任:实现必须检查在proc上应用了上面描述的生存期的限制。一个实现可以在应用之前检查proc是不是一个合适的参数。
(string-copy string) 过程
返回给定string的一个新分配的副本。
11.13. 向量
向量是一个异构结构(heterogeneous structures),其元素由整数索引。向量通常比相同长度的表占据更少的空间,访问随机选定的元素时,向量需要的平均时间通常也比表少。
一个向量的长度是其包含的元素的数量。这个数量是一个非负整数,其在向量创建的时候就被确定。一个向量的合法索引是小于向量长度的精确非负整数对象。在向量中,第一个元素的索引是零,且最后一个元素的索引比向量的长度小一。
和表常量一样,向量常量必须被引用(quoted):
'#(0 (2 2 2 2) "Anna")
⇒ #(0 (2 2 2 2) "Anna")
(vector? obj) 过程
如果obj是一个向量则返回#t,否则过程返回#f。
(make-vector k) |
过程 |
(make-vector k fill) |
过程 |
返回一个新分配的k个元素的向量。如果给了第二个参数,那么每个元素被初始化为fill。否则每个元素的初始化内容是未定义的。
(vector obj ...) 过程
返回一个新分配的向量,其元素包含给定的参数。类似与list。
(vector 'a 'b 'c) ⇒ #(a b c)
(vector-length vector) 过程
以一个精确整数对象的形式返回vector中元素的数量。
(vector-ref vector k) 过程
K必须是vector的一个合法的索引。vector-ref过程返回vector元素k的内容。
(vector-ref '#(1 1 2 3 5 8 13 21) 5)
⇒ 8
(vector-set! vector k obj) 过程
K必须是vector的一个合法的索引。vector-set!过程将obj存储到vector的元素k中,且返回未定义的值。
给vector-set!传递一个不可改变的向量应当导致一个条件类型是&assertion的异常被抛出。
(let ((vec (vector 0 '(2 2 2 2) "Anna")))
(vector-set! vec 1 '("Sue" "Sue"))
vec)
⇒ #(0 ("Sue" "Sue") "Anna")
(vector-set! '#(0 1 2) 1 "doe")
⇒ 未定义的
; 常向量
; 应当抛出&assertion异常
(vector->list vector) |
过程 |
(list->vector list) |
过程 |
vector->list过程返回一个新分配的由包含在vector中元素的对象组成的表。list->vector过程返回一个新创建的由表list中的元素初始化的向量。
(vector->list '#(dah dah didah))
⇒ (dah dah didah)
(list->vector '(dididit dah))
⇒ #(dididit dah)
(vector-fill! vector fill) 过程
将fill存储到vector的每一个元素中,且返回未定义的值。
\(\texttt{(vector-map proc $vector_1$ $vector_2$ ...)}\) 过程
所有的vector必须有相同的长度。Proc应该接受和vector个数相同的参数,且返回一个单独的值。
vector-map过程将proc逐个元素地应用到vector的元素上且返回一个按顺序的结果的向量。Proc总是在相同的vector-map本身的动态环境中被调用。proc应用到vector元素的顺序是未定义的。如果从vector-map出现多个返回,早期返回的返回值不会被改变。
类似于map。
实现责任:实现必须检查在proc上应用了上面描述的生存期的限制。一个实现可以在应用之前检查proc是不是一个合适的参数。
\(\texttt{(vector-for-each proc $vector_1$ $vector_2$ ...)}\) 过程
所有的vector必须有相同的长度。Proc应该接受和vector个数相同的参数。vector-for-each过程将proc逐个元素地应用到vector的字符上,其以副作用为目的,并且是按照从第一个字符到最后一个的顺序。Proc总是在相同的vector-for-each本身的动态环境中被调用。vector-for-each的返回值是未定义的。
类似于for-each。
实现责任:实现必须检查在proc上应用了上面描述的生存期的限制。一个实现可以在应用之前检查proc是不是一个合适的参数。
11.14. 错误(Errors)和违规行为(violations)
\(\texttt{(error $who$ $message$ $irritant_1$ ...)}\) |
过程 |
\(\texttt{(assertion-violation $who$ $message$ $irritant_1$ ...)}\) |
过程 |
Who必须是一个字符串或一个符号或#f。Message必须是一个字符串。Irritant是任意的对象。
这些过程抛出一个异常。当一个错误出现的时候,error过程应当被调用,通常由程序在与外部世界或用户交互时已经出错的一些事情导致。assertion-violation过程在一个过程被非法调用的时候应当被调用,或者是传递了非法数量的参数,或者传递了未被指定处理的参数。
Who参数应当描述检测到异常的过程或操作。Message参数应当应当描述外部情况。Irritant应当是检测到操作的操作的参数。
异常提供的条件对象(见库的第7章)有下列的条件类型:
- 如果who不是
#f,那么条件有条件类型&who,who作为它的域的值。在哪种情况下,who应当是检测到异常的过程或条目的名字。如果它是#f,那么条件没有条件类型&who。 - 条件有条件类型
&message,message作为它的域的值。 - 条件有条件类型
&irritant,irritant的表作为它的域的值。
此外,error创建的条件有条件类型&error,且assertion-violation创建的条件有条件类型&assertion。
(define (fac n)
(if (not (integer-valued? n))
(assertion-violation
'fac "non-integral argument" n))
(if (negative? n)
(assertion-violation
'fac "negative argument" n))
(letrec
((loop (lambda (n r)
(if (zero? n)
r
(loop (- n 1) (* r n))))))
(loop n 1)))
(fac 5) ⇒ 120
(fac 4.5) ⇒ &assertion异常
(fac -3) ⇒ &assertion异常
(assert <expression>) 语法
一个assert形式通过计算<expression>被求值。如果<expression>返回一个真值,那么那个值被从assert表达式返回。如果<expression>返回#f,那么一个条件类型是&assertion和&message的异常被抛出。条件对象提供的消息是实现定义的。
注意:实现应当利用这样的事实,assert是提供尽可能多关于断言(assertion)错误位置信息的语法。
11.15. 控制特性
本章描述了各种以特殊方式控制程序执行流程的基本过程。
\(\texttt{(apply proc $arg_1$ ... rest-args)}\) 过程
rest-args必须是一个表。Proc应当接受n个元素,其中n是arg的数量加上rest-args的长度。apply过程以表\(\texttt{(append (list $arg_1$ ...) rest-args)}\)的元素作为真实参数调用proc。
如果一个apply的调用在尾上下文中,那么对proc的调用也在尾上下文中。
(apply + (list 3 4)) ⇒ 7
(define compose
(lambda (f g)
(lambda args
(f (apply g args)))))
((compose sqrt *) 12 75) ⇒ 30
(call-with-current-continuation proc) |
过程 |
(call/cc proc) |
过程 |
Proc应当接受一个参数。过程call-with-current-continuation(这和过程call/cc是一样的)将当前的继续作为“逃逸过程”打包,且作为参数传递给proc。这个逃逸过程是一个Scheme过程,如果它在随后被调用的话,将会放弃任何在随后起作用的继续,取而代之以恢复逃逸过程创建时起作用的继续。调用逃逸过程可以导致dynamic-wind安装的before和after过程被调用。
逃逸过程的参数数量与最初调用call-with-current-continuation时的继续的参数数量相同。
传入proc的逃逸过程像其他Scheme过程那样拥有无限的生存期。它可以被存入变量或数据结构中,可以被调用任意多次。
如果对call-with-current-continuation的调用发生在尾上下文中,那么对proc的调用也在尾上下文中。
以下示例只显示了call-with-current-continuation的一些用法。如果所有真实的应用都像这些示例一样简单,像call-with-current-continuation这样强大的过程也就没有存在的必要了。
(call-with-current-continuation
(lambda (exit)
(for-each (lambda (x)
(if (negative? x)
(exit x)))
'(54 0 37 -3 245 19))
#t)) ⇒ -3
(define list-length
(lambda (obj)
(call-with-current-continuation
(lambda (return)
(letrec ((r
(lambda (obj)
(cond ((null? obj) 0)
((pair? obj)
(+ (r (cdr obj)) 1))
(else (return #f))))))
(r obj))))))
(list-length '(1 2 3 4)) ⇒ 4
(list-length '(a b . c)) ⇒ #f
(call-with-current-continuation procedure?)
⇒ #t
注意:调用一个逃逸过程会从新进入调用call-with-current-continuation的动态生存期,且因此恢复它的动态环境;见第5.12小节。
(values obj ...) 过程
把所有的参数传递给它的继续。values过程可以像如下方式定义:
(define (values . things)
(call-with-current-continuation
(lambda (cont) (apply cont things))))
一系列中表达式中所有非最终表达式的继续,比如在lambda, begin, let, let*, letrec, letrec*, let-values, let*-values, case, 和cond形式中,通常接受任意数量的值。
除了这些和call-with-values, let-values, 以及let*-values创建的继续,继续隐式地接受一个单独的值,比如<operator>和过程调用的<operator>或在条件语句中的<text>表达式的继续,都精确地接受一个值。给继续传递不当数量的值的效果是未定义的。
(call-with-values producer consumer) 过程
Producer必须是一个过程,且必须接受零个值。Consumer必须是一个过程,且必须接受和produce过程返回值个数一样多的值。call-with-values过程会调用producer,调用时没有参数且有如下继续,当传递给这个继续一些值,会以这些值作为参数调用consumer过程。consumer调用的继续和call-with-values调用的继续相同。
(call-with-values (lambda () (values 4 5))
(lambda (a b) b))
⇒ 5
(call-with-values * -) ⇒ -1
如果call-with-values调用发生在尾上下文中,那么consumer调用也在尾上下文中。
实现责任:在producer返回后,实现必须检查consumer必须接受和producer返回值一样多的值。
(dynamic-wind before thunk after) 过程
Before, thunk, 和after必须是过程,且每个都应当接受零个参数。这些过程可以返回任意数量的值。dynamic-wind以无参数的方式调用thunk,返回这个调用的结果。此外,每当进入trunk调用的动态生存期的时候,dynamic-wind会以无参数的方式调用before,每当离开trunk调用的动态生存期的时候,dynamic-wind会以无参数的方式调用after。因此,在缺少call-with-current-continuation创建的逃逸过程调用的情况下,dynamic-wind按顺序调用before, thunk, after。
尽管对before和after的调用不被认为是在thunk调用的动态生存期内,但是,出现在thunk调用动态生存期内的任何其它的dynamic-wind调用的before和after过程被认为是在thunk调用的动态生存期里面的。
更精确地说,一个逃逸过程将控制权转移出一组零个或多个活动的dynamic-wind调用x……的动态生存期且将控制权转移入一组零个或多个活动的dynamic-wind调用y……它离开最近x的动态生存期,且以无参数的方式调用其对应的after过程。如果after过程返回的话,逃逸过程继续到下一个最近的x,以此类推。一旦每一个x都以这种方式被处理了,逃逸过程以无参数的方式调用最不接近的y对应的before过程。如果before过程返回的话,逃逸过程重新进入最近y的动态生存期,然后继续处理下一个最近的y,以此类推。一旦每一个y都以这种方式被处理了,控制权被转移到逃逸过程打包的继续。
实现责任:实现必须检查在thunk和after上的限制,只要它们被实际调用。
(let ((path '())
(c #f))
(let ((add (lambda (s)
(set! path (cons s path)))))
(dynamic-wind
(lambda () (add 'connect))
(lambda ()
(add (call-with-current-continuation
(lambda (c0)
(set! c c0)
'talk1))))
(lambda () (add 'disconnect)))
(if (< (length path) 4)
(c 'talk2)
(reverse path))))
⇒ (connect talk1 disconnect
connect talk2 disconnect)
(let ((n 0))
(call-with-current-continuation
(lambda (k)
(dynamic-wind
(lambda ()
(set! n (+ n 1))
(k))
(lambda ()
(set! n (+ n 2)))
(lambda ()
(set! n (+ n 4))))))
n) ⇒ 1
(let ((n 0))
(call-with-current-continuation
(lambda (k)
(dynamic-wind
values
(lambda ()
(dynamic-wind
values
(lambda ()
(set! n (+ n 1))
(k))
(lambda ()
(set! n (+ n 2))
(k))))
(lambda ()
(set! n (+ n 4))))))
n) ⇒ 7
注意:进入一个动态生存期会恢复它的动态环境;见第5.12小节。
11.16. 迭代
(let <variable> <bindings> <body>) 语法
“命名let(Named let)”是let在语法上的一个变体,其提供一个通用的循环结构,同时也被用作表达递归。除了<variable>用<body>绑定到一个过程之外,它和普通的let有相同的语法和语义,其中这个过程的变量是被绑定的变量,内部是<body>。因此,<body>的执行可以通过调用<variable>命名的过程被重复。
(let loop ((numbers '(3 -2 1 6 -5))
(nonneg '())
(neg '()))
(cond ((null? numbers) (list nonneg neg))
((>= (car numbers) 0)
(loop (cdr numbers)
(cons (car numbers) nonneg)
neg))
((< (car numbers) 0)
(loop (cdr numbers)
nonneg
(cons (car numbers) neg)))))
⇒ ((6 1 3) (-5 -2))
11.17. 准引用(Quasiquotation)
(quasiquote <qq template>) |
语法 |
unquoteauxiliary |
辅助语法 |
unquote-splicingauxiliary |
辅助语法 |
“反引用(Backquote)”或“准引用”在构造表或向量结构时是有用的,尤其是在需要的结构中只有一些是提前知道的时候。
语法:<Qq template>应该与本条目最后的语法说明的一样。
语义:如果没有unquote或unquote-splicing形式出现在<qq template>里面,那么(quasiquote <qq template>)计算结果和(quote <qq template>)计算结果是一样的。
如果一个(unquote <expression> ...)形式出现在<qq template>里面,那么<expression>会被求值(“解引用(unquoted)”)且它们的结果会被插入到结构中来代替unquote形式。
如果一个(unquote-splicing <expression> ...)形式出现在<qq template>里面,那么<expression>的结果必须是一个表;然后,表中的左右小括号会被“剥离(stripped away)”,且表中的元素会被插入到结构中来代替unquote-splicing形式。
任何unquote-splicing或多操作的unquote形式智能出现在一个表或向量的<qq template>中。
正如第4.3.5小节所提到的,(quasiquote <qq template>)可以被简写成`<qq template>,(unquote <expression>)可以被简写成,<expression>,且(unquote-splicing <expression>)可以被简写成,@<expression>。
`(list ,(+ 1 2) 4) ⇒ (list 3 4)
(let ((name 'a)) `(list ,name ',name))
⇒ (list a (quote a))
`(a ,(+ 1 2) ,@(map abs '(4 -5 6)) b)
⇒ (a 3 4 5 6 b)
`(( foo ,(- 10 3)) ,@(cdr '(c)) . ,(car '(cons)))
⇒ ((foo 7) . cons)
`#(10 5 ,(- 4) ,@(map - '(16 9)) 8)
⇒ #(10 5 -4 -16 -9 8)
(let ((name 'foo))
`((unquote name name name)))
⇒ (foo foo foo)
(let ((name '(foo)))
`((unquote-splicing name name name)))
⇒ (foo foo foo)
(let ((q '((append x y) (sqrt 9))))
``(foo ,,@q))
⇒ `(foo
(unquote (append x y) (sqrt 9)))
(let ((x '(2 3))
(y '(4 5)))
`(foo (unquote (append x y) (- 9)))) ;(已根据勘误表修改)
⇒ (foo (2 3 4 5) -9)
准引用格式可以嵌套。置换操作只作用于那些与最外层quasiquote有同样嵌套级别的解除引用的元素。每进入一个后续的准引用,嵌套级别就增加一,每进入一个解除引用的语法单元,嵌套级别就减少一。
`(a `(b ,(+ 1 2) ,(foo ,(+ 1 3) d) e) f)
⇒ (a `(b ,(+ 1 2) ,(foo 4 d) e) f)
(let ((name1 'x)
(name2 'y))
`(a `(b ,,name1 ,',name2 d) e))
⇒ (a `(b ,x ,'y d) e)
一个quasiquote表达式可以返回新鲜的,可变的结构,也可以返回字面结构,其中每个结构都是在表达式求值期间在运行时被构造。不需要被重建的部分总是字面的。因此,
(let ((a 3)) `((1 2) ,a ,4 ,'five 6))
可以等价于下面两个表达式之一:
'((1 2) 3 4 five 6)
(let ((a 3))
(cons '(1 2)
(cons a (cons 4 (cons 'five '(6))))))
可是,它不等价于这个表达式:
(let ((a 3)) (list (list 1 2) a 4 'five 6))
<Qq template>中的quasiquote、unquote或
unquote-splicing标识符出现在不符合上述描述的其它位置是一个语法错误。
下面准引用表达式的语法不是上下文无关的。他作为生成无限数量产生式规则的一个配方。想象在D = 1, 2, 3, …时下列规则的副本。D跟踪嵌套深度。
<qq template> → <qq template 1>
<qq template 0> → <expression>
<quasiquotation D> → (quasiquote <qq template D>)
<qq template D> → <lexeme datum>
∣ <list qq template D>
∣ <vector qq template D>
∣ <unquotation D>
<list qq template D> → (<qq template or splice D>*)
∣ (<qq template or splice D>+ . <qq template D>)
∣ <quasiquotation D + 1>
<vector qq template D> → #(<qq template or splice D>*)
<unquotation D> → (unquote <qq template D−1>)
<qq template or splice D> → <qq template D>
∣ <splicing unquotation D>
<splicing unquotation D> →
(unquote-splicing <qq template D−1>*)
∣ (unquote <qq template D−1>*)
在<quasiquotation>中,<list qq template D>有时会和<unquotation D>或<splicing unquotation D>混淆。解释为<unquotation D>或<splicing unquotation D>是优先的。
11.18. 句法关键词的绑定结构
let-syntax和letrec-syntax形式绑定关键词。就像begin形式一样,一个let-syntax或letrec-syntax形式可以出现在定义上下文中,在这种情况下,它被当作一个定义,且在内部的形式必须也是定义。一个let-syntax或letrec-syntax形式也可以出现在一个表达式上下文中,在这种情况下,它们内部的形式必须是表达式。
(let-syntax <bindings> <form> ...) 语法
语法:<Bindings>必须有如下形式:
((<keyword> <expression>) ...)
每个<keyword>是一个标识符,且每个<expression>是一个在宏扩展时期可以被求值为转换器的表达式。转换器可以通过syntax-rules或identifier-syntax(见第11.19小节)创建,或通过库的第12章描述的其它机制之一创建。在关键词绑定列表中<keyword>出现超过一个是一个语法语法错误。
语义:<Form>在语法环境中被扩展,此句法环境通过扩展let-syntax形式的句法环境以宏的方式获得,其关键词是<keyword>,绑定到特定的转换器。每个<keyword>的绑定以<form>作为它的作用范围。
不管是在定义还是在表达式上下文中,let-syntax形式中的<form>被当作在一个隐含的begin中;见第11.4.7小节。因此,在<form>扩展结果中的定义和出现在let-syntax形式中的定义有相同的作用范围。
实现责任:实现应当检测<expression>的值是否有可能不是一个转换器。
(let-syntax ((when (syntax-rules ()
((when test stmt1 stmt2 ...)
(if test
(begin stmt1
stmt2 ...))))))
(let ((if #t))
(when if (set! if 'now))
if)) ⇒ now
(let ((x 'outer))
(let-syntax ((m (syntax-rules () ((m) x))))
(let ((x 'inner))
(m)))) ⇒ outer
(let ()
(let-syntax
((def (syntax-rules ()
((def stuff ...) (define stuff ...)))))
(def foo 42))
foo) ⇒ 42
(let ()
(let-syntax ())
5) ⇒ 5
(letrec-syntax <bindings> <form> ...) 语法
语法:和let-syntax一样。
语义:<Form>在语法环境中被扩展,此句法环境通过扩展letrec-syntax形式的句法环境以宏的方式获得,其关键词是<keyword>,绑定到特定的转换器。每个<keyword>的绑定以<bindings>以及<form>作为它的作用范围,所以,转换器可以将形式转译成通过letrec-syntax形式引入的宏的使用。
不管是在定义还是在表达式上下文中,letrec-syntax形式中的<form>被当作在一个隐含的begin中;见第11.4.7小节。因此,在<form>扩展结果中的定义和出现在letrec-syntax形式中的定义有相同的作用范围。
实现责任:实现应当检测<expression>的值是否有可能不是一个转换器。
(letrec-syntax
((my-or (syntax-rules ()
((my-or) #f)
((my-or e) e)
((my-or e1 e2 ...)
(let ((temp e1))
(if temp
temp
(my-or e2 ...)))))))
(let ((x #f)
(y 7)
(temp 8)
(let odd?)
(if even?))
(my-or x
(let temp)
(if y)
y))) ⇒ 7
下面的例子突出了let-syntax和letrec-syntax的不同。
(let ((f (lambda (x) (+ x 1))))
(let-syntax ((f (syntax-rules ()
((f x) x)))
(g (syntax-rules ()
((g x) (f x)))))
(list (f 1) (g 1))))
⇒ (1 2)
(let ((f (lambda (x) (+ x 1))))
(letrec-syntax ((f (syntax-rules ()
((f x) x)))
(g (syntax-rules ()
((g x) (f x)))))
(list (f 1) (g 1))))
⇒ (1 1)
除了第一个表达式中的let-syntax形式和第二个当中的letrec-syntax之外,两个表达式是一样的。在第一个表达式中,出现在g中的f引用let绑定中的变量f,而在第二个当中它引用的关键词f其绑定是letrec-syntax形式建立的。
11.19. 宏转换器
(syntax-rules (<literal> ...) <syntax rule> ...) |
语法(扩展) |
_ |
辅助语法(扩展) |
... |
辅助语法(扩展) |
语法:每一个<literal>必须是一个标识符。每一个<syntax rule>必须有如下形式:
(<srpattern> <template>)
一个<srpattern>是<pattern>的受限形式,也就是说,一个非空的<pattern>是下列的四个由小括号包围的形式之一,其第一个子形式是一个标识符或一个下划线_。一个<pattern>是一个标识符,常量,或下面当中的一个。
(<pattern> ...)
(<pattern> <pattern> ... . <pattern>)
(<pattern> ... <pattern> <ellipsis> <pattern> ...)
(<pattern> ... <pattern> <ellipsis> <pattern> ... . <pattern>)
#(<pattern> ...)
#(<pattern> ... <pattern> <ellipsis> <pattern> ...)
一个<ellipsis>是标识符“...”(三个句号(译注:必须是西文句号))。<Template>是一个模式变量,一个不是模式变量的标识符,一个模式数据,或下列形式之一。
(<subtemplate> ...)
(<subtemplate> ... . <template>)
#(<subtemplate> ...)
一个<subtemplate>是一个<template>跟着零个或多个省略号(译注:必须是西文省略号)。
语义:在宏扩展期间,syntax-rules的实例通过指定一系列的卫生重写规则被求值为一个新的宏转换器。一个关键词与syntax-rules指定的转换器相关联的宏的使用与<syntax rule>包含的模式相匹配,以最左边的<syntax rule>开始。当发现一个匹配的时候,宏使用根据模板被卫生地转录。没有匹配是一个语法错误。
出现在<pattern>中的标识符可以是一个下划线(_),一个列在字面量(<literal> ...)的表中的字面量标识符,或一个省略号(...)。所有出现在<pattern>中的其它标识符都是模式变量(pattern variables)。省略号或下划线出现在(<literal> ...)中是一个语法错误。
尽管<srpattern>的第一个子形式可以是一个标识符,但是标识符不涉及到匹配,且不被认为是一个模式变量或一个字面量标识符。
模式变量匹配任意的输入子形式且被用作引用输入的元素。在<pattern>中同样的模式变量出现超过一次是一个语法错误。
下划线也匹配任意的输入子形式,但不是模式变量,且不能被用作引用那些元素。<Pattern>中可以出现多个下划线。
一个字面量标识符匹配一个输入子形式当且仅当输入子形式是一个标识符且或者在输入表达式中它的出现和在字面量的表中它的出现有相同的词法绑带,或者两个标识符有相同的名字且都没有词法绑定。
一个跟着一个省略号的子模式可以匹配零个或多个输入元素。
更正式地说,一个输入形式F匹配一个模式P当且仅当下列之一成立:
- P是一个下划线(
_)。 - P是一个模式变量。
- P是一个字面量标识符且F是一个标识符,其使得如果P和F都出现在被插入宏输出的任何绑定的外部的宏的输出中,那么它们都指向相同的绑定。(如果两个类似命名的标识符没有一个指向任意的绑定,也就是说,两个都是未定义的,它们被认为是指向相同的绑定。)
- P有形式
\(\texttt{($P_1$ ... $P_n$)}\),且F是一个有n个元素的表,其分别与\(P_1\)到\(P_n\)匹配。 - P有形式
\(\texttt{($P_1$ ... $P_n$ . $P_x$)}\),且F是一个有n个元素或多于n个元素的表或非严格表,其前n个元素分别与\(P_1\)到\(P_n\)匹配,且其第n个cdr匹配\(P_x\)。 - P有形式
\(\texttt{($P_1$ ... $P_k$ $P_e$ <ellipsis> $P_{m+1}$ ... $P_n$)}\),其中<ellipsis>是标识符...,且F是一个有n个元素的表,其前k个元素分别匹配\(P_1\)到\(P_k\),接下来的m - k个元素匹配\(P_e\),且剩余的n - m个元素匹配\(P_{m+1}\)到\(P_n\)。 - P有形式
\(\texttt{($P_1$ ... $P_k$ $P_e$ <ellipsis> $P_{m+1}$ ... $P_n$ . $P_x$)}\),其中<ellipsis>是标识符...,且F是一个有n个元素的表或非严格表,其前k个元素分别匹配\(P_1\)到\(P_k\),接下来的m - k个元素匹配\(P_e\),且剩余的n - m个元素匹配\(P_{m+1}\)到\(P_n\),且其第n个和最终的cdr匹配\(P_x\)。 - P有形式
\(\texttt{#($P_1$ ... $P_n$)}\),且F是一个有n个元素的向量,其分别与\(P_1\)到\(P_n\)匹配。 - P有形式
\(\texttt{#($P_1$ ... $P_k$ $P_e$ <ellipsis> $P_{m+1}$ ... $P_n$)}\),其中<ellipsis>是标识符...,且F是一个有n个元素或多余n个元素的向量,其前k个元素分别匹配\(P_1\)到\(P_k\),接下来的m - k个元素匹配\(P_e\),且剩余的n - m个元素匹配\(P_{m+1}\)到\(P_n\)。 - P是一个模式数据(任何非表,非向量,非符号数据)且F在过程
equal?的意义下等价于P。
当一个宏使用根据匹配的<syntax rule>模板转录的时候,出现在模板中的模式变量被输入中匹配的子形式替代。
不是模式变量或省略号的模式数据和标识符被复制到输出中。一个跟着一个省略号的子模版扩展成一个或多个子模版的出现。出现在子模式并跟着一个或多个省略号的模式变量只可以出现在跟着(至少)何其一样多省略号的子模版中。这些模式变量在输出中被替换成它们绑定的按指定分布的输入子形式。如果一个模式变量在子模版中比在与其相关联的子模式跟着更多的省略号,那么输入形式根据需要重复。子模版必须至少包含一个来自跟着一个省略号的子模式的模式变量,且至少有一个这样的模式变量,其子模版必须跟着和其中出现模式变量的子模式中省略号数量精确相等的省略号。(否则,扩展器可能不能决定子形式应该在输出中重复多少次。)不符合本段的约束是一个语法错误。
形式(<ellipsis> <template>)的模板和<template>是相同的,除了模板中的省略号没有特殊的意义之外。换言之,任何包含在<template>中的省略号被当作普通的标识符对待。特别地,模板(... ...)产生一个单独的省略号,...。这允许句法抽象扩展包含省略号的形式。
(define-syntax be-like-begin
(syntax-rules ()
((be-like-begin name)
(define-syntax name
(syntax-rules ()
((name expr (... ...))
(begin expr (... ...))))))))
(be-like-begin sequence)
(sequence 1 2 3 4) ⇒ 4
考虑一个辅助标识符卫生使用的例子,如果let和cond按第11.16小节和附录B所定义,那么它们是卫生的(和要求一样),且下面的不是一个错误。
(let ((=> #f))
(cond (#t => 'ok))) ⇒ ok
cond的宏转换器注意到=>是一个局部变量,且因此是一个表达式,而不是标识符=>,宏转换器将其当作句法关键词对待。因此例子扩展成
(let ((=> #f))
(if #t (begin => 'ok)))
而不是
(let ((=> #f))
(let ((temp #t))
(if temp ('ok temp))))
后者可以导致一个断言违规。
\(
\begin{array}{lr}
\texttt{(identifier-syntax $<template>$)} & 语法(扩展)\
\begin{array}{l}
\texttt{(identifier-syntax} \
\quad\quad\texttt{($<id_1>$ $<template_1>$)} \
\quad\quad\texttt{((set! $<id_2>$ $<pattern>$)} \
\quad\quad\quad\texttt{$<template_2>$))}
\end{array} & 语法(扩展)\
\texttt{set!} & 辅助语法(扩展)
\end{array}
\)
语法:<Id>必须是标识符。<Template>必须为syntax-rules。
语义:当一个关键词被绑定到一个由identifier-syntax的第一个形式产生的转换器的时候,关键词的引用在绑定的范围内被<template>代替。
(define p (cons 4 5))
(define-syntax p.car (identifier-syntax (car p)))
p.car ⇒ 4
(set! p.car 15) ⇒ &syntax(语法)异常
更常用的,identifier-syntax的第二个形式允许转换器决定当set!被使用时会发生什么。在这种情况下,标识符它自己的使用被\(<template_1>\)代替,和标识符一起set!的使用被\(<template_2>\)代替。
(define p (cons 4 5))
(define-syntax p.car
(identifier-syntax
(_ (car p))
((set! _ e) (set-car! p e))))
(set! p.car 15)
p.car ⇒ 15
p ⇒ (15 . 5) ; (已根据勘误表修改)
11.20. 尾调用和尾上下文
尾调用是一个发生在尾上下文中的过程调用。尾上下文被以归纳的方式定义。注意,对于一个特定的lambda表达式,其尾上下文总是确定的。
- 一个lambda表达式内部的最后一个表达式,即下面用
<tail expression>表示的,是在尾上下文中。
(lambda <formals>
<definition>*
<expression>* <tail expression>)
- 如果下面表达式的一个在尾上下文中,那么用
<tail expression>表示的子表达式也在尾上下文中。这些来源于本章中所描述的形式语法规范,把有些<expression>出现的地方改成了<tail expression>。只有那些包含尾上下文的规则被列在了这儿。
(if <expression> <tail expression> <tail expression>)
(if <expression> <tail expression>)
(cond <cond clause>+)
(cond <cond clause>* (else <tail sequence>))
(cāse <expression>
<case clause>+)
(cāse <expression>
<case clause>*
(else <tail sequence>))
(and <expression>* <tail expression>)
(or <expression>* <tail expression>)
(let <bindings> <tail body>)
(let <variable> <bindings> <tail body>)
(let* <bindings> <tail body>)
(letrec* <bindings> <tail body>)
(letrec <bindings> <tail body>)
(let-values <mv-bindings> <tail body>)
(let*-values <mv-bindings> <tail body>)
(let-syntax <bindings> <tail body>)
(letrec-syntax <bindings> <tail body>)
(begin <tail sequence>)
一个<cond clause>是
(<test> <tail sequence>),
一个<case clause>是
((<datum>*) <tail sequence>),
一个<tail body>是
<definition>* <tail sequence>,
且一个<tail sequence>是
<expression>* <tail expression>。
- 如果一个
cond表达式在尾上下文中,且有一个\(\texttt{($<expression_1>$ => $<expression_2>$)}\)形式的子句,那么对\(<expression_2>\)求值结果的过程的(隐含)调用在尾上下文中。\(<Expression_2>\)它自己不在尾上下文中。
特定的内置过程也必须执行尾调用。传递给apply和call-with-current-continuation的第一个参数,和传递给call-with-values的第二个参数必须通过尾调用被调用。
在下面的例子中,唯一的尾调用是f调用。g和h调用都不是尾调用。x的引用在尾上下文中,但它不是一个调用,因此不是一个尾调用。
(lambda ()
(if (g)
(let ((x (h)))
x)
(and (g) (f))))
注意:实现可以把一些非尾调用,比如上面的h调用,当作尾调用来求值。在上面的例子中,let表达式可以以尾调用f的方式来编译。(h返回不确定数量值得可能性可以被忽略,这时因为在那种情况下let的作用明显是未定义且依赖于实现的。)