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
中包含 printf
的 make-add-suffix
版本
目前正在阅读 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
printf
的 make-add-suffix
版本