在本页中:
4.5.1 函数简写
4.5.2 柯里化函数简写
4.5.3 多值与 define-values
4.5.4 内部定义

4.5 定义:define

基本定义的形式为

(define id expr)

此时 id 会被绑定到 expr 的结果上。

例如:
(define salutation (list-ref '("Hi" "Hello") (random 2)))
> salutation

"Hello"

4.5.1 函数简写

define 形式也支持函数定义的简写:

(define (id arg ...) body ...+)

它简写自

(define id (lambda (arg ...) body ...+))

例如:
(define (greet name)
  (string-append salutation ", " name))
> (greet "John")

"Hello, John"

(define (greet first [surname "Smith"] #:hi [hi salutation])
  (string-append hi ", " first " " surname))

 

> (greet "John")

"Hello, John Smith"

> (greet "John" #:hi "Hey")

"Hey, John Smith"

> (greet "John" "Doe")

"Hello, John Doe"

通过 define 定义的函数简写也支持剩余参数 (即,最后一个参数将额外的参数收集在一个列表中):

(define (id arg ... . rest-id) body ...+)

它简写自

(define id (lambda (arg ... . rest-id) body ...+))

例如:
(define (avg . l)
  (/ (apply + l) (length l)))
> (avg 1 2 3)

2

4.5.2 柯里化函数简写

考虑以下 make-add-suffix 函数,它接受一个字符串并返回另一个接受字符串的函数:

(define make-add-suffix
  (lambda (s2)
    (lambda (s) (string-append s s2))))

 

尽管它并不常见,但 make-add-suffix 的结果可被直接调用,就行这样:

> ((make-add-suffix "!") "hello")

"hello!"

某种意义上来说,make-add-suffix 是个接受两个参数的函数, 不过它一次只接受其中的一个。接受一部分参数并返回另一个函数来消耗更多参数的函数, 叫做柯里化函数

使用 define 的函数简写形式,make-add-suffix 可等价地写作

(define (make-add-suffix s2)
  (lambda (s) (string-append s s2)))

此简写反映了函数调用 (make-add-suffix "!") 的形状。define 形式进一步还支持定义柯里化函数的简写,该简写反映了嵌套的函数调用:

(define ((make-add-suffix s2) s)
  (string-append s s2))

 

> ((make-add-suffix "!") "hello")

"hello!"

(define louder (make-add-suffix "!"))
(define less-sure (make-add-suffix "?"))

 

> (less-sure "really")

"really?"

> (louder "really")

"really!"

define 的函数简写的完整语法如下:

(define (head args) body ...+)
 
head = id
  | (head args)
     
args = arg ...
  | arg ... . rest-id

此简写展开后,定义中的每一个 head 都对应一层嵌套的 lambda 形式,最内层的 head 对应最外层的 lambda

4.5.3 多值与 define-values

Racket 表达式通常只会产生一个结果,然而某些表达式可以产生多个结果。例如, quotientremainder 二者均会产生一个值,而 quotient/remainder 则会一次产生与前二者结果相同的两个值:

> (quotient 13 3)

4

> (remainder 13 3)

1

> (quotient/remainder 13 3)

4

1

如上所示,REPL 会在单独的行中打印每个值。

多值函数可通过 values 函数来实现,该函数接受任意数量的值并将它们作为结果返回:

> (values 1 2 3)

1

2

3

(define (split-name name)
  (let ([parts (regexp-split " " name)])
    (if (= (length parts) 2)
        (values (list-ref parts 0) (list-ref parts 1))
        (error "not a <first> <last> name"))))

 

> (split-name "Adam Smith")

"Adam"

"Smith"

define-values 形式可将单个表达式产生的多个结果一次绑定到多个标识符:

(define-values (id ...) expr)

expr 产生的结果数量必须与 id 的数量相匹配。

例如:
(define-values (given surname) (split-name "Adam Smith"))
> given

"Adam"

> surname

"Smith"

非函数简写的 define 形式等价于只有一个 iddefine-values 形式。

+The Racket ReferenceDefinitions: define, define-syntax, ...一节中提供了关于定义的更多信息。

4.5.4 内部定义

当一个语法形式的文法中指定了 body 时,其对应的形式可以是定义或表达式。 作为 body 的定义叫做 内部定义

body 序列中的表达式和内部定义可以混合使用,只要最后一个 body 是表达式就行。

例如,lambda 的语法为

(lambda gen-formals
  body ...+)

因此下面是有效的文法实例:

(lambda (f)                ; no definitions
  (printf "running\n")
  (f 0))
 
(lambda (f)                ; one definition
  (define (log-it what)
    (printf "~a\n" what))
  (log-it "running")
  (f 0)
  (log-it "done"))
 
(lambda (f n)              ; two definitions
  (define (call n)
    (if (zero? n)
        (log-it "done")
        (begin
          (log-it "running")
          (f n)
          (call (- n 1)))))
  (define (log-it what)
    (printf "~a\n" what))
  (call n))

特定的 body 序列中的内部定义是可以互相递归的,也就是说, 任何定义都可以引用任何其它定义——只要引用在其定义之前不会被实际求值就行。 若定义被引用得太早,就会产生错误。

例如:
(define (weird)
  (define x x)
  x)
> (weird)

x: undefined;

 cannot use before initialization

一系列只使用 define 的内部定义很容易被翻译成等价的 letrec 形式(下一节会介绍)。然而,其它定义形式可作为 body 出现,包括 define-valuesstruct(见 Programmer-Defined Datatypes)或 define-syntax(见 Macros)。

+The Racket ReferenceInternal Definitions一节中阐述了内部定义的要点。