在本页中:
4.3.1 求值顺序与参数量
4.3.2 关键字参数
4.3.3 apply 函数

4.3 函数调用(过程应用)

对于形式为

(proc-expr arg-expr ...)

的表达式,当 proc-expr 不是作为语法变换器(如 ifdefine)来绑定的标识符时,它就是一个函数调用,另称为过程应用

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 ReferenceProcedure 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"))