从 Curry-0、1、2 到 ...n
Going from Curry-0, 1, 2, to ...n
根据我之前询问的有关编写 curry 函数的问题,,我已经开始编写 0、1、2 的固定大小写——它们与教会数字非常相似,即挺整洁的。这是我目前所拥有的:
(define (curry-0 func)
func)
(define hello (begin (display "Hello!") (newline)))
(curry-0 hello)
; Hello!
(define (curry-1 func)
(lambda (x)
(func x )))
((curry-1 -) 2)
; -2
(define (curry-2 func)
(lambda (x)
(lambda (y)
(func x y))))
(((curry-2 +) 2) 3)
5
模式似乎是:
(define curry-n func
(lambda (x1)
(lambda (x2)
...
(lambda (xn)
(func x1 x2 ... xn)
但是,我在 'building up' n 嵌套 lambda 表达式时遇到了一些问题。我想我可以用类似的东西构建一个嵌套的 lambda:
(define (curry-n num func)
(if (num > 0)
(lambda (?) (curry-n (- num 1) func))))
但我不确定如何 'bind' 每个 lambda 函数的变量以及最终如何将这些相同的变量(按顺序)传递给 (func x1 x2 ... xn)
函数。
这是不正确的,但我是按照...
开始的
(define (curry-n num func)
(if (> num 0)
; but lambda won't accept a number, possible to string-format?
(curry-n (- num 1) (lambda (num)))))
这是怎么做到的?
我建议您改为编写以下函数:
;; create `num` nested lambdas, where the body applies `func` to
;; `args-so-far` along with the arguments of those nested lambdas
(define (curry-n* func num args-so-far)
...)
这是它的用法:
(define (sub-4 a b c d)
(- a b c d))
(curry-n* sub-4 0 (list 10 1 2 3))
;=> should evaluate to 10 - 1 - 2 - 3 = 4
(curry-n* sub-4 1 (list 10 1 2))
;=> should evaluate to a procedure that accepts x,
; and returns 10 - 1 - 2 - x = 7 - x
(curry-n* sub-4 2 (list 10 1))
;=> should evaluate to a procedure that accepts x,
; and returns a procedure that accepts y,
; and returns 10 - 1 - x - y = 9 - x - y
对于基本情况 (num
= 0),您需要使用函数 apply
.
一旦你有了 curry-n*
,你就可以创建 curry-n
,它调用 curry-n*
作为辅助函数。
一旦你有了解决方案,你会发现它不是很有效。你可以把curry-n*
修改为翻转args-fo-far
,这样它的用法是这样的:
(define (sub-4 a b c d)
(- a b c d))
(curry-n* sub-4 0 (list 3 2 1 10))
;=> should evaluate to 4
(curry-n* sub-4 1 (list 2 1 10))
;=> should evaluate to a procedure that accepts x,
; and returns 7 - x
(curry-n* sub-4 2 (list 1 10))
;=> should evaluate to a procedure that accepts x,
; and returns a procedure that accepts y,
; and returns 9 - x - y
使用循环
您需要某种 loop
来从 lambda
-
中收集每个 arg
(define (curry-n num f)
(let loop
((n num)
(args null))
(lambda ((arg null))
(cond ((= n 0)
(apply f (reverse args)))
((= n 1)
(apply f (reverse (cons arg args))))
(else
(loop (- n 1) (cons arg args)))))))
curry
应该alwaysreturn一个程序,所以可以看到loop
会always return a lambda
,即使是 num = 0
的情况。每个 arg
都被 cons
加到 args
上,创建一个反向的参数列表。这就是为什么我们 reverse
args
在 apply
用户提供的过程 f
.
之前
它是这样工作的-
(define (hello)
(println "hello world"))
((curry-n 0 hello))
"hello world"
((((curry-n 3 +) 10) 20) 30)
60
使用定界延续
本着分享学习练习的精神,花点时间用delimited continuations-
复习一下这个例子
(require racket/control)
(define (curry-3 f)
(reset (f (shift k k) (shift k k) (shift k k))))
(define (hello a b c)
(printf "hello world ~a ~a ~a\n" a b c))
((((curry-3 hello) 10) 20) 30)
hello world 10 20 30
要获得 curry-n
,我们需要做的就是构建一个包含 n
个延续的列表!
(require racket/control)
(define (curry-n n f)
(reset (apply f (build-list n (lambda (_) (shift k k))))))
它的工作原理和我们的第一个一样 -
(define (hello a b c)
(printf "hello world ~a ~a ~a\n" a b c))
((((curry-n 3 hello) 10) 20) 30)
"hello world 10 20 30"
((((((curry-n 5 +) 10) 20) 30) 40) 50)
150
你可以把这个过程想象成这样,其中每个 __
都是一个需要填充的“洞” -
(f __ __ __)
\
\_ (lambda (x)
(f x __ __))
\
\_ (lambda (y)
(f x y __))
\
\_ (lambda (z)
(f x y z))
唯一的区别是有n
个洞需要填补,所以我们构建了一个包含n
个洞和apply
-
的列表
(build-list 3 (lambda (_) (shift k k)))
(list __
\
\_ (lambda (x)
(list x __
\
\_ (lambda (y)
(list x y __
\
\_ (lambda (z)
(list x y z))
(apply f (list x y z)
方案
我没有注意到你在 Scheme 中做这项工作。我们可以定义 build-list
-
(define (build-list n f)
(let loop
((m 0))
(if (>= m n)
'()
(cons (f m) (loop (+ m 1))))))
我们可以使用 Olivier Danvy 的原始实现 shift/reset,
(define-syntax reset
(syntax-rules ()
((_ ?e) (reset-thunk (lambda () ?e)))))
(define-syntax shift
(syntax-rules ()
((_ ?k ?e) (call/ct (lambda (?k) ?e)))))
(define *meta-continuation*
(lambda (v)
(error "You forgot the top-level reset...")))
(define abort
(lambda (v)
(*meta-continuation* v)))
(define reset-thunk
(lambda (t)
(let ((mc *meta-continuation*))
(call-with-current-continuation
(lambda (k)
(begin
(set! *meta-continuation* (lambda (v)
(begin
(set! *meta-continuation* mc)
(k v))))
(abort (t))))))))
(define call/ct
(lambda (f)
(call-with-current-continuation
(lambda (k)
(abort (f (lambda (v)
(reset (k v)))))))))
我们的curry-n
可以保持原样-
(define (curry-n n f)
(reset (apply f (build-list n (lambda (_) (shift k k))))))
((((((curry-n 5 +) 10) 20) 30) 40) 50)
150
其他答案可能会更有效一些,因为它们积累了一个具体的参数列表,或者更漂亮,因为它们允许您一次接受多个参数,但在我看来,这是一个更好的学习练习,而且更简单,只需编写一个一次接受一个参数的基本函数,而不需要累加器。
(define (curry n f)
(cond ((= n 1) f)
(else (lambda (x)
(curry (- n 1)
(lambda args
(apply f (cons x args))))))))
> (((curry 2 +) 5) 4)
=> 9
我们只是创建一个每次期望更少参数的新 lambda,然后柯里化它,而不是累积参数。它的行为非常类似于您对 fixed n
的尝试的概括。它与 rawrex 的(完全合理的)答案的基本方法相同,但没有处理更大块中的参数的奇特设施。
根据我之前询问的有关编写 curry 函数的问题,
(define (curry-0 func)
func)
(define hello (begin (display "Hello!") (newline)))
(curry-0 hello)
; Hello!
(define (curry-1 func)
(lambda (x)
(func x )))
((curry-1 -) 2)
; -2
(define (curry-2 func)
(lambda (x)
(lambda (y)
(func x y))))
(((curry-2 +) 2) 3)
5
模式似乎是:
(define curry-n func
(lambda (x1)
(lambda (x2)
...
(lambda (xn)
(func x1 x2 ... xn)
但是,我在 'building up' n 嵌套 lambda 表达式时遇到了一些问题。我想我可以用类似的东西构建一个嵌套的 lambda:
(define (curry-n num func)
(if (num > 0)
(lambda (?) (curry-n (- num 1) func))))
但我不确定如何 'bind' 每个 lambda 函数的变量以及最终如何将这些相同的变量(按顺序)传递给 (func x1 x2 ... xn)
函数。
这是不正确的,但我是按照...
开始的(define (curry-n num func)
(if (> num 0)
; but lambda won't accept a number, possible to string-format?
(curry-n (- num 1) (lambda (num)))))
这是怎么做到的?
我建议您改为编写以下函数:
;; create `num` nested lambdas, where the body applies `func` to
;; `args-so-far` along with the arguments of those nested lambdas
(define (curry-n* func num args-so-far)
...)
这是它的用法:
(define (sub-4 a b c d)
(- a b c d))
(curry-n* sub-4 0 (list 10 1 2 3))
;=> should evaluate to 10 - 1 - 2 - 3 = 4
(curry-n* sub-4 1 (list 10 1 2))
;=> should evaluate to a procedure that accepts x,
; and returns 10 - 1 - 2 - x = 7 - x
(curry-n* sub-4 2 (list 10 1))
;=> should evaluate to a procedure that accepts x,
; and returns a procedure that accepts y,
; and returns 10 - 1 - x - y = 9 - x - y
对于基本情况 (num
= 0),您需要使用函数 apply
.
一旦你有了 curry-n*
,你就可以创建 curry-n
,它调用 curry-n*
作为辅助函数。
一旦你有了解决方案,你会发现它不是很有效。你可以把curry-n*
修改为翻转args-fo-far
,这样它的用法是这样的:
(define (sub-4 a b c d)
(- a b c d))
(curry-n* sub-4 0 (list 3 2 1 10))
;=> should evaluate to 4
(curry-n* sub-4 1 (list 2 1 10))
;=> should evaluate to a procedure that accepts x,
; and returns 7 - x
(curry-n* sub-4 2 (list 1 10))
;=> should evaluate to a procedure that accepts x,
; and returns a procedure that accepts y,
; and returns 9 - x - y
使用循环
您需要某种 loop
来从 lambda
-
arg
(define (curry-n num f)
(let loop
((n num)
(args null))
(lambda ((arg null))
(cond ((= n 0)
(apply f (reverse args)))
((= n 1)
(apply f (reverse (cons arg args))))
(else
(loop (- n 1) (cons arg args)))))))
curry
应该alwaysreturn一个程序,所以可以看到loop
会always return a lambda
,即使是 num = 0
的情况。每个 arg
都被 cons
加到 args
上,创建一个反向的参数列表。这就是为什么我们 reverse
args
在 apply
用户提供的过程 f
.
它是这样工作的-
(define (hello)
(println "hello world"))
((curry-n 0 hello))
"hello world"
((((curry-n 3 +) 10) 20) 30)
60
使用定界延续
本着分享学习练习的精神,花点时间用delimited continuations-
复习一下这个例子(require racket/control)
(define (curry-3 f)
(reset (f (shift k k) (shift k k) (shift k k))))
(define (hello a b c)
(printf "hello world ~a ~a ~a\n" a b c))
((((curry-3 hello) 10) 20) 30)
hello world 10 20 30
要获得 curry-n
,我们需要做的就是构建一个包含 n
个延续的列表!
(require racket/control)
(define (curry-n n f)
(reset (apply f (build-list n (lambda (_) (shift k k))))))
它的工作原理和我们的第一个一样 -
(define (hello a b c)
(printf "hello world ~a ~a ~a\n" a b c))
((((curry-n 3 hello) 10) 20) 30)
"hello world 10 20 30"
((((((curry-n 5 +) 10) 20) 30) 40) 50)
150
你可以把这个过程想象成这样,其中每个 __
都是一个需要填充的“洞” -
(f __ __ __)
\
\_ (lambda (x)
(f x __ __))
\
\_ (lambda (y)
(f x y __))
\
\_ (lambda (z)
(f x y z))
唯一的区别是有n
个洞需要填补,所以我们构建了一个包含n
个洞和apply
-
(build-list 3 (lambda (_) (shift k k)))
(list __
\
\_ (lambda (x)
(list x __
\
\_ (lambda (y)
(list x y __
\
\_ (lambda (z)
(list x y z))
(apply f (list x y z)
方案
我没有注意到你在 Scheme 中做这项工作。我们可以定义 build-list
-
(define (build-list n f)
(let loop
((m 0))
(if (>= m n)
'()
(cons (f m) (loop (+ m 1))))))
我们可以使用 Olivier Danvy 的原始实现 shift/reset,
(define-syntax reset
(syntax-rules ()
((_ ?e) (reset-thunk (lambda () ?e)))))
(define-syntax shift
(syntax-rules ()
((_ ?k ?e) (call/ct (lambda (?k) ?e)))))
(define *meta-continuation*
(lambda (v)
(error "You forgot the top-level reset...")))
(define abort
(lambda (v)
(*meta-continuation* v)))
(define reset-thunk
(lambda (t)
(let ((mc *meta-continuation*))
(call-with-current-continuation
(lambda (k)
(begin
(set! *meta-continuation* (lambda (v)
(begin
(set! *meta-continuation* mc)
(k v))))
(abort (t))))))))
(define call/ct
(lambda (f)
(call-with-current-continuation
(lambda (k)
(abort (f (lambda (v)
(reset (k v)))))))))
我们的curry-n
可以保持原样-
(define (curry-n n f)
(reset (apply f (build-list n (lambda (_) (shift k k))))))
((((((curry-n 5 +) 10) 20) 30) 40) 50)
150
其他答案可能会更有效一些,因为它们积累了一个具体的参数列表,或者更漂亮,因为它们允许您一次接受多个参数,但在我看来,这是一个更好的学习练习,而且更简单,只需编写一个一次接受一个参数的基本函数,而不需要累加器。
(define (curry n f)
(cond ((= n 1) f)
(else (lambda (x)
(curry (- n 1)
(lambda args
(apply f (cons x args))))))))
> (((curry 2 +) 5) 4)
=> 9
我们只是创建一个每次期望更少参数的新 lambda,然后柯里化它,而不是累积参数。它的行为非常类似于您对 fixed n
的尝试的概括。它与 rawrex 的(完全合理的)答案的基本方法相同,但没有处理更大块中的参数的奇特设施。