4.1 标记法
这一章(以及其余的文档)使用了一个稍微不同的标记法,而不是基于字符的《Racket概要》章里的语法。对于一个句法表something的使用表现为如下方式:
(something [id ...+] an-expr ...)
在本规范中斜体的元变量,如id和an-expr,使用Racket标识的语法,所以an-expr是一元变量。一个命名约定隐式地定义了许多元变量的含义:
-
一个以id结束的元变量代表一个标识,如x或my-favorite-martian。
-
一个以keyword结束的元标识代表一个关键字,如#:tag。
-
一个以expr结束的元标识代表任意子表,它将被解析为一个表达式。
-
一个以body结束的元标识代表任意子表;它将被解析为一个局部定义或者一个表达式。一个body只有不被任何表达式前置时才能解析为一个定义,并且最后一个body必须是一个表达式;参见《内部定义》部分。
在语法中的方括号表示表的一个括号序列,这里方括号通常被使用(约定)。也就是说,方括号并不表示是句法表的可选部分。
一个...表示前置表的零个或多个重复,...+表示前置数据的一个或多个重复。另外,非斜体标识代表它们自己。
那么,基于上面的语法,这里有一些something的与以上相符合的用法:
(something [x]) (something [x] (+ 1 2)) (something [x my-favorite-martian x] (+ 1 2) #f)
一些语法表规范指既不是隐式定义的也不是预定义的元变量。这样的元变量在主表后面定义,使用一个BNF-like表提供选择:
(something-else [thing ...+] an-expr ...)
thing = thing-id | thing-keyword
上面的例子表明,在一个something-else表中,一个thing要么是一个标识要么是一个关键字。
一个表达式的上下文决定表达式中出现的标识的含义。特别是,用语言racket开始一个模块时,如:
#lang racket
意味着,在模块中,标识在本指南中的描述开始于这里意义的描述:cons引用创建了一个序对的函数,car引用提取了一个序对的第一个元素的函数,等等。
《符号(Symbol)》介绍了标识语法。
诸如define、lambda和let之类的表,用一个或多个标识关联一个意义;也就是说,它们绑定(bind)标识。绑定应用的程序部分是绑定的范围(scope)。对一个给定的表达式有效的绑定集是表达式的环境(environment)。
例如,有以下内容:
#lang racket (define f (lambda (x) (let ([y 5]) (+ x y)))) (f 10)
define是f的绑定,lambda有一个对x的绑定,let有一个对y的绑定,对f的绑定范围是整个模块;x绑定的范围是(let ([y 5]) (+ x y));y绑定的范围仅仅是(+ x y)的环境包括对y、x和f的绑定,以及所有在racket中的绑定。
一个模块级的define仅能够绑定没有被定义过或者require进模块的标识。然而,一个局部define或其它绑定表,能够给一个已经有一个绑定的标志符以一个新的局部绑定;这样的一个绑定覆盖(shadows)已经存在的绑定。
Examples:
(define f (lambda (append) (define cons (append "ugly" "confusing")) (let ([append 'this-was]) (list append cons)))) > (f list) '(this-was ("ugly" "confusing"))
类似地,一个模块级define可以从这个模块的语言覆盖一个绑定。例如,一个racket模块里的(define cons 1)覆盖被racket提供的cons。故意覆盖一个语言绑定绝对是一个好主意——尤其对于像cons这种被广泛使用的绑定——但是覆盖把一个程序员从不得不去避免每一个晦涩的通过一个语言提供的绑定中解脱出来。
即使像define和lambda这些从绑定中得到它们的意义,尽管它们有转换器(transformer)绑定(这意味着它们表明语法表)而不是值绑定。由于define有一个转换器绑定,这个标识define不能被它自己使用于获取一个值。然而,对define的常规绑定可以被覆盖。
Examples:
> define eval:1:0: define: bad syntax
in: define
> (let ([define 5]) define) 5
同样,用这种方式来覆盖标准绑定绝对是一个好主意,但这种可能性是Racket的灵活性一个固有部分。
表的一个表达式:
(proc-expr arg-expr ...)
是一个函数调用——也被称为一个应用程序(procedure application)——当proc-expr不是一个被绑定为一个语法翻译器(如if或define)的标识符时。
一个函数调用通过首先求值proc-expr并都按顺序(由左至右)来求值。然后,如果arg-expr产生一个接受arg-expr提供的所有参数的函数,这个函数被调用。否则,将引发一个异常。
Examples:
> (cons 1 null) '(1)
> (+ 1 2 3) 6
> (cons 1 2 3) cons: arity mismatch;
the expected number of arguments does not match the given
number
expected: 2
given: 3
> (1 2 3) application: not a procedure;
expected a procedure that can be applied to arguments
given: 1
某些函数,如cons,接受一个固定数量的参数。某些函数,如+或list,接受任意数量的参数。一些函数接受一系列参数计数;例如substring既接受两个参数也接受三个参数。一个函数的实参数量(arity)是它接受参数的数量。
除了通过位置参数外,有些函数接受关键字参数(keyword arguments)。因此,一个arg可以是一个arg-keyword arg-expr序列而不仅仅只是一个arg-expr:
《关键字(Keyword)》介绍了关键字。
(proc-expr arg ...)
arg = arg-expr | arg-keyword arg-expr
例如:
(go "super.rkt" #:mode 'fast)
用"super.rkt"作为一个位置参数调用这个函数绑定到go,并用'fast作为一个参数与#:mode关键字关联。一个关键字隐式地与它后面的表达式序对。
既然一个关键字本身不是一个表达式,那么
(go "super.rkt" #:mode #:fast)
就是一个语法错误。#:mode关键字必须跟着一个表达式以产生一个参数值,并且#:fast不是一个表达式。
关键字arg的顺序决定arg-expr求值的顺序,而一个函数接受关键字参数不依赖于参数列表中的位置。上面对go的调用可以等价地编写为:
(go #:mode 'fast "super.rkt")
在《Racket参考》的“(application)”部分提供了有关过程程序的更多信息。
函数调用的语法支持任意数量的参数,但是一个特定的调用总是指定一个固定数量的参数。因此,一个带一个参数列表的函数不能直接应用一个类似于+的函数到一个列表的所有项中:
(define (avg lst) ; 不会运行…… (/ (+ lst) (length lst)))
> (avg '(1 2 3)) +: contract violation
expected: number?
given: '(1 2 3)
(define (avg lst) ; 不总会运行…… (/ (+ (list-ref lst 0) (list-ref lst 1) (list-ref lst 2)) (length lst)))
> (avg '(1 2 3)) 2
> (avg '(1 2)) list-ref: index too large for list
index: 2
in: '(1 2)
apply函数提供了一种绕过这种限制的方法。它使用一个函数和一个list参数,并将函数应用到列表中的值:
(define (avg lst) (/ (apply + lst) (length lst)))
> (avg '(1 2 3)) 2
> (avg '(1 2)) 3/2
> (avg '(1 2 3 4)) 5/2
为方便起见,apply函数接受函数和列表之间的附加参数。额外的参数被有效地cons到参数列表:
(define (anti-sum lst) (apply - 0 lst))
> (anti-sum '(1 2 3)) -6
apply函数也接受关键字参数,并将其传递给调用函数:
(apply go #:mode 'fast '("super.rkt")) (apply go '("super.rkt") #:mode 'fast)
包含在apply的列表参数中的关键字不算作调用函数的关键字参数;相反,这个列表中的所有参数都被作为位置参数对待。要将一个关键字参数列表传递给一个函数,使用keyword-apply函数,它接受一个要应用的函数和三个列表。前两个列表是平行的,其中第一个列表包含关键字(按keyword<?排序),第二个列表包含一个与每个关键字对应的参数。第三个列表包含位置函数参数,就像apply。
(keyword-apply go '(#:mode) '(fast) '("super.rkt"))