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

Posted

技术标签:

【中文标题】是否可以在球拍中创建匿名递归函数【英文标题】:is it possible to create an anonymous recursive function in racket 【发布时间】:2022-01-19 20:25:04 【问题描述】:

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

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

如何制作它的 lambda 版本而不给它命名? ...就像我想在某处内联它一样。那可能吗? (我的意思是在这种情况下我可以使用折叠 - 所以这个例子可能不是那么好) - 是否有某种我无法找到的“自我”符号或占位符?或者你只需​​要给它一个名字。

【问题讨论】:

注意:我将这个问题标记为“lambda”——这很有趣,因为我猜有人会听所有与 lambda 相关的问题,不管是什么语言——但考虑一下……如果有python,common lisp,clojure 的答案......不是 js - 他们只是有一个特殊的插槽,你可以在其中为你的无名函数命名......但其他语言 “命名让”怎么样? ***.com/a/3739297/17635987 你的意思是,像Y-Combinator?这样你就可以只使用匿名 lambdas 编写递归过程。 【参考方案1】:

Racket 中的 Y-Combinator 是:

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

这个函数可以接受任何匿名函数并递归地应用到自己身上。

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

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

f 我们可以随意命名 - 所以我们也可以称它为 double-n-part

如果我们对此应用 Y-Combinator,我们得到:

((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

【讨论】:

【参考方案2】:

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

如果您不想用名称污染命名空间,您可以简单地使用命名为 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

对于实际的答案:这是一个宏rlam,它类似于lambda,但它允许您使用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

【讨论】:

是的 - 我猜更多的污染 - 似乎很奇怪有一个你不能内联的东西。 我什至建议 ((let () (define (foo x n) ... foo ...) foo) 10 10) 创建(然后使用)匿名 lambda。我猜这就是你的 Racket 宏正在做的事情。【参考方案3】:

是的。作为名称的占位符是 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)) ))))

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

(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" 将 lambdas 嵌套到一个普通的可调用二进制 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 参数将其关闭即可,@987654337 @,从而使此绑定匿名,即使该名称无法从外部观察到;然后通过 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

内部名称和全局名称再次相互独立。

【讨论】:

以上是关于是否可以在球拍中创建匿名递归函数的主要内容,如果未能解决你的问题,请参考以下文章

在递归函数 C++ 中创建向量

匿名递归 PHP 函数

递归函数匿名函数

七 递归与二分法匿名函数内置函数

函数递归+匿名函数+内置函数day15

如何在Python中创建一个递归函数来创建一个映射Odoo 8关系字段记录的字典?