4.3 函数调用(过程应用)
对于形式为
(proc-expr arg-expr ...)
的表达式,当 proc-expr 不是作为语法变换器(如 if 或 define)来绑定的标识符时,它就是一个函数调用,另称为过程应用。
4.3.1 求值顺序与参数量
函数调用首先会按从左到右的顺序对 proc-expr 和所有的 arg-expr 进行求值。接着,如果 proc-expr 产生的函数所接受的参数数量与 arg-expr 所支持的相同,那么该函数就会被调用。否则就会触发一个异常。
> (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
arguments...:
1
2
3
> (1 2 3) application: not a procedure;
expected a procedure that can be applied to arguments
given: 1
arguments...:
2
3
有些函数如 cons 接受固定数量的参数;有些函数如 + 或 list 则接受任意数量的参数。还有些函数接受一定数量范围内的参数, 例如 substring 接受两个或三个参数。 函数的参数量(或称元数)即为该函数所接受的参数数量。
4.3.2 关键字参数
除了位置固定的参数外,有些函数还接受关键字参数。此时,arg 可以是一个 arg-keyword arg-expr 序列而非只是一个 arg-expr:
关键字介绍了关键字。
(proc-expr arg ...)
arg = arg-expr | arg-keyword arg-expr
例如,
(go "super.rkt" #:mode 'fast)
会以 "super.rkt" 作为位置固定的参数,以 'fast 作为关联到 #:mode 关键字的参数,调用绑定到 go 的函数。 关键字会隐式地与紧随其后的表达式配对。
由于关键字本身并非表达式,因此
(go "super.rkt" #:mode #:fast)
会产生语法错误。#:mode 关键字必须后跟一个表达式来产生一个参数值, 然而 #:fast 并不是一个表达式。
关键字 arg 的顺序决定了 arg-expr 求值的顺序, 然而函数接受的关键字参数与其在参数列表中的位置无关。上面对 go 的调用可以等价地写作:
(go #:mode 'fast "super.rkt")
The Racket Reference的Procedure Applications and #%app一节中提供了关于过程应用的更多信息。
4.3.3 apply 函数
函数调用的语法支持任意数量的参数,然而具体的调用总是指定固定数量的参数。 结果就是,接受一个参数列表的函数无法直接将 + 这类的函数应用到列表中所有的项上:
(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 函数提供了一种绕过此限制的方式。它接受一个函数和一个 列表参数,并将该函数应用到列表中的值上:
(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"))