是否可以在球拍中创建匿名递归函数



如果我有一个这样的递归函数:

(define (double-n-times x n)
(if (= n 0)
x
(double-n-times (* 2 x) (- n 1))))

我怎样才能制作它的 lambda 版本并且从不给它起名字?就像我想在某个地方内联它一样。 这可能吗?(我的意思是在这种情况下我可以使用折叠 - 所以也许这个例子不是那么好) - 是否有某种我无法找到的"自我"符号或占位符?或者你只需要给它一个名字。

Racket 中的 Y-Combinator 是:

(lambda (f)
((lambda (h) (h h))
(lambda (g) (f (lambda args (apply (g g) args))))))

此函数可以采用任何匿名函数并将其递归地应用于自身。

让我们定义您的函数部分。double-n-times-仅用lambda编写的部分:

(lambda (f)
(lambda (x n)
(if (= n 0) x (f (* 2 x) (- n 1))))))

我们可以随心所欲地命名f- 所以我们也可以称它为double-n-part.

如果我们对此应用 Y-组合器,我们会得到:

((lambda (f)
((lambda (h) (h h))
(lambda (g) (f (lambda args (apply (g g) args))))))
(lambda (f)
(lambda (x n)
(if (= n 0) x (f (* 2 x) (- n 1))))))

这会吐出一个函数,该函数将参数xn,并将第二个定义的内部函数应用于它们。

所以现在,没有任何命名函数 - 仅使用lambda表达式 - 您可以应用于您的参数 - 假设x=3n=4

(((lambda (f)
((lambda (h) (h h))
(lambda (g) (f (lambda args (apply (g g) args))))))
(lambda (f)
(lambda (x n)
(if (= n 0) x (f (* 2 x) (- n 1))))))
3 4)
;;=> 48 ; as expected (3 * 2 * 2 * 2 * 2)

这样阅读起来更方便。 但是,当我们只允许一元函数(具有一个参数的函数)而不是可变参数函数时,我们也可以在没有applyargs的情况下定义Y combinator。然后它看起来像这样(我们必须像这样一个接一个地给出论点):

((((lambda (f)
((lambda (h) (h h))
(lambda (g) (f (lambda (x) ((g g) x))))))
(lambda (f)
(lambda (x)
(lambda (n)
(if (= n 0) x ((f (* 2 x)) (- n 1)))))))
3) 4)
;;=> 48

通过使用宏,您的问题的答案是肯定的。但在我谈论这个问题之前,我必须先问这个问题:你问是因为你只是好奇吗?还是因为存在一些问题而问,例如您不想用名称污染命名空间?

如果你不想用名称污染命名空间,你可以简单地使用本地构造,如命名letletrec,甚至Y组合器。或者,您可以将define包装在(let () ...)内。

(let ()
(define (double-n-times x n)
(if (= n 0)
x
(double-n-times (* 2 x) (- n 1))))
(double-n-times 10 10))
;; double-n-times is not in scope here

对于实际答案:这是一个类似于lambda的宏rlam,但它允许您使用self来引用自身:

#lang racket
(require syntax/parse/define)
(define-syntax-parse-rule (rlam args body ...+)
#:with self (datum->syntax this-syntax 'self)
(letrec ([self (λ args body ...)])
self))
;; compute factorial of 10
((rlam (x)
(if (= 0 x)
1
(* x (self (sub1 x))))) 10) ;=> 3628800

是的。作为名称的占位符是lambda 函数的参数的用途:

(define (double-n-times x n)
(if (= n 0)
x
(double-n-times (* 2 x) (- n 1))))
=
(define double-n-times (lambda (x n)
(if (= n 0)
x
(double-n-times (* 2 x) (- n 1)))))
=
(define double-n-times (lambda (self)   ;; received here
(lambda (x n)
(if (= n 0)
x
(self (* 2 x) (- n 1))))))       ;; and used, here

但是这个"self"参数是什么? 它是lambda函数本身

= ;; this one's in error...
(define double-n-times ((lambda (u)   ;; call self with self
(u u))   ;; to receive self as an argument
(lambda (self)
(lambda (x n)
(if (= n 0)
x
(self (* 2 x) (- n 1)))))))
;; ...can you see where and why?
= ;; this one isn't:
(define double-n-times ((lambda (u) (u u))
(lambda (self)
(lambda (x n)
(if (= n 0)
x
((self self) (* 2 x) (- n 1)))))))
;; need to call self with self to actually get that 
;; (lambda (x n) ... ) thing to be applied to the values!

现在它起作用了:(double-n-times 1.5 2)返回6.0.


这已经很好了,但是我们必须在那里写((self self) ... ...)来表达二进制递归调用。我们能做得更好吗?我们可以像以前一样使用常规(self ... ...)调用语法编写 lambda 函数吗?我看看。是吗

= ;; erroneous
(define double-n-times ((lambda (u) (u u))
(lambda (self)
(lambda (x n)
(lambda (rec body) (self self)
(if (= n 0)
x
(rec (* 2 x) (- n 1))))))))

(不)还是

= ;; also erroneous...
(define double-n-times ((lambda (u) (u u))
(lambda (self)
(lambda (x n)
((lambda (rec body) body)
(self self)
(if (= n 0)
x
(rec (* 2 x) (- n 1))))))))   ;; ...can you see why?

(仍然没有)还是也许

= ;; still erroneous...
(define double-n-times ((lambda (u) (u u))
(lambda (self)
((lambda (rec)
(lambda (x n)
(if (= n 0)
x
(rec (* 2 x) (- n 1)))))
(self self) ))))

(又不...以一种有趣的方式)或者它实际上是

=
(define double-n-times ((lambda (u) (u u))
(lambda (self)
((lambda (rec)
(lambda (x n)
(if (= n 0)
x
(rec (* 2 x) (- n 1)))))
(lambda (a b) ((self self) a b)) ))))

(是的!)这样它就可以被抽象并分离成

(define (Y2 g) ((lambda (u) (u u))
(lambda (self)
(g
(lambda (a b) ((self self) a b))))))
(define double-n-times (Y2
(lambda (rec)      ;; declare the rec call name
(lambda (x n)
(if (= n 0)
x
(rec (* 2 x) (- n 1)))))))       ;; and use it to make the call

这就是 Scheme 严格求值策略下二进制函数的Y组合子。

因此,我们首先使用我们选择的递归调用名称关闭我们的二进制 lambda 函数,然后使用Y2组合器将这个"rec spec">嵌套的 lambda 转换为一个普通的可调用二进制lambda函数(即需要两个参数)。

当然,名称本身并不重要,只要它不干扰我们代码中的其他名称,它就rec。特别是以上也可以写成

(define double-n-times                          ;; globally visible name
(Y2
(lambda (double-n-times)    ;; separate binding,
(lambda (x n)            ;;    invisible from
(if (= n 0)                                   ;;    the outside
x
(double-n-times (* 2 x) (- n 1)))))))     ;; original code, unchanged

定义与结果完全相同的函数。

这样,我们根本不需要更改原始代码,只需使用另一个与我们预期的递归调用名称相同的lambda参数关闭它,double-n-times,从而使此绑定匿名,即使该名称从外部无法观察到;然后通过Y2组合器传递它。


当然 Scheme 已经有递归绑定了,我们可以通过使用letrec来达到相同的效果:

(define double-n-times           ;; globally visible name
(letrec ((double-n-times       ;; internal recursive binding:
(lambda (x n)       ;; its value, (lambda (x n) ...)
(if (= n 0)   
x
(double-n-times (* 2 x) (- n 1))))))
double-n-times))            ;; internal binding's value

同样,内部名称和全局名称彼此独立。

最新更新