Racket 中 lambda 函数的求值顺序

Evaluation order of lambda functions in Racket

目前正在阅读 https://docs.racket-lang.org 的 Racket Guide 并阅读 lambda 函数。对它们的用途的解释很清楚,但我不确定我是否完全掌握了评估这些函数的顺序。考虑指南中的以下示例:

(define (twice f v)
  (f (f v)))

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

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

此处对 twice 的函数调用计算结果为 "hello!!"。这是我对评估过程的猜测:

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

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

((make-add-suffix "!") (string-append "hello" "!"))

(string-append (string-append "hello" "!") "!")

(string-append "hello!" "!")

"hello!!"

这是一个准确的评估,还是我遗漏了什么?

Slogan:最外层和最左边的嵌套表达式应该先求值

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

; (define (f x) ...) is short for (define f (lambda (x) ...)) so,
; = { substitute twice with  (lambda (f v) (f (f v)))}

((lambda (f v) (f (f v))) (make-add-suffix "!") "hello")

; = { substition of make-add-suffix with (lambda (s2) (lambda (s) (string-append s s2)))}

((lambda (f v) (f (f v)))
 ((lambda (s2) (lambda (s) (string-append s s2))) "!")
 "hello")

我们继续之前的术语:

  • Beta 减少: ((lambda (x-1 ... x-n) f-body) v-1 ... v-n) == f-body 所有出现的 x-1 ... x-n 分别替换为v-1 ... v-n

  • 按值调用:函数调用的参数在 beta 缩减之前计算。

; = { beta-reduction of ((lambda (s2) (lambda (s) (string-append s s2))) "!") }

((lambda (f v) (f (f v))) (lambda (s) (string-append s "!")) "hello")

; = { beta-reduction of the whole expression }

((lambda (s) (string-append s "!"))
 ((lambda (s) (string-append s "!")) "hello"))

; = { beta-reduction of the expression in the argument position first }

((lambda (s) (string-append s "!")) (string-append "hello" "!"))

; ... and the rest is easy:
((lambda (s) (string-append s "!")) "hello!")
(string-append "hello!" "!")
"hello!!"

获得相同答案的不同方式:DrRacket 包含一个 "Stepper" 工具,正是为了这个目的。如果将语言级别设置为 "Intermediate Student with Lambda" 并单击 "Step" 按钮,您应该能够将程序的评估视为一系列步骤,如 thrvshl 所述。

编辑:您描述的评估策略,其中 twice 的第一个参数替换 twice 定义中 x 的每个实例,称为 "call-by-name" 评估,并与懒惰 a la Haskell 相关联。要查看差异,请考虑在内部 lambda

中包含 printfmake-add-suffix 版本