仅具有 lambda 表达式的阶乘函数

发布于 2024-10-20 14:51:47 字数 203 浏览 2 评论 0原文

您可以仅使用 lambda 表达式自行创建的最透明、最优雅的阶乘函数是什么?

我的一个学生在伯克利参加了一个计划课程,并获得了仅使用 lambda 表达式(没有定义、let 或其他幂过程)创建阶乘函数的额外学分问题。我花了一段时间才解决,而且很复杂而且很难看。

几年后,我现在正在教授Scheme,我意识到我要把它作为对自己的挑战,并认为其他人也可能会欣赏它。

What is the most transparent and elegant factorial function you can create, on your own, using only lambda expressions?

One of my students took a Scheme class at Berkeley and was given this extra credit problem of creating the factorial function with only lambda expressions (no define, let, or other power procedures). It took me awhile to solve, and was complicated and ugly.

I am teaching Scheme now, a couple of years later, and I realized I was going to set it as a challenge for myself, and thought others might appreciate it as well.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(6

愁杀 2024-10-27 14:51:47

这是一个(柯里化的)版本:

((lambda (x) (x x))
 (lambda (fact-gen)
   (lambda (n)
     (if (zero? n)
         1
         (* n ((fact-gen fact-gen) (sub1 n)))))))

尾递归版本:

(let ((fact-gen
       (lambda (fact-gen n acc)
         (if (zero? n)
             acc
             (fact-gen fact-gen (sub1 n) (* n acc))))))
  (lambda (n) (fact-gen fact-gen n 1)))

关于教堂数字:

(let* ((one (lambda (s z) (s z)))
       (add1 (lambda (n) (lambda (s z) (s (n s z)))))
       (* (lambda (a b) (lambda (s z) (a (lambda (z2) (b s z2)) z))))
       (cons (lambda (a b) (lambda (f) (f a b)))))
  (lambda (n)
    ((n (lambda (p)
          (p (lambda (count acc)
               (cons (add1 count) (* count acc)))))
        (cons one one))
     (lambda (a b) b))))

Here's one (curried) version:

((lambda (x) (x x))
 (lambda (fact-gen)
   (lambda (n)
     (if (zero? n)
         1
         (* n ((fact-gen fact-gen) (sub1 n)))))))

Tail-recursive version:

(let ((fact-gen
       (lambda (fact-gen n acc)
         (if (zero? n)
             acc
             (fact-gen fact-gen (sub1 n) (* n acc))))))
  (lambda (n) (fact-gen fact-gen n 1)))

On Church numerals:

(let* ((one (lambda (s z) (s z)))
       (add1 (lambda (n) (lambda (s z) (s (n s z)))))
       (* (lambda (a b) (lambda (s z) (a (lambda (z2) (b s z2)) z))))
       (cons (lambda (a b) (lambda (f) (f a b)))))
  (lambda (n)
    ((n (lambda (p)
          (p (lambda (count acc)
               (cons (add1 count) (* count acc)))))
        (cons one one))
     (lambda (a b) b))))
三月梨花 2024-10-27 14:51:47

这是我能想到的最简单的尾递归版本:

(lambda (n)
  (((lambda (!) (! !))
    (lambda (!)
      (lambda (n acc)
        (if (zero? n)
            acc
            ((! !) (sub1 n) (* n acc))))))
   n 1))

很难在更少的空间中获得递归。自应用程序必须发生在某个地方,并且大多数按值调用语言(如Scheme)中的独立固定点都必须引入额外的 lambda 以避免自应用程序中的失控递归。

相反,我的解决方案和 Jeremiah 的解决方案将自我应用程序隐藏在Scheme 的短路 if 的一个分支中,从而以少得多的字符提供必要的递归。

Here's the simplest tail-recursive version I can think of:

(lambda (n)
  (((lambda (!) (! !))
    (lambda (!)
      (lambda (n acc)
        (if (zero? n)
            acc
            ((! !) (sub1 n) (* n acc))))))
   n 1))

It's hard to get recursion in less space. The self-application has to happen somewhere, and most standalone fixpoints in a call-by-value language like Scheme have to introduce extra lambdas to avoid runaway recursion at the self-application.

Instead, my solution and Jeremiah's hide the self-application in one branch of Scheme's short-circuit if, giving the necessary recursion with far fewer characters.

瞄了个咪的 2024-10-27 14:51:47

我几年前做的一个台词数量是我几年前的两倍,而且更难理解。

(lambda (n)
  ((lambda (fact) (fact fact 1 n))
   (lambda (f P n) (if (<= n 1) P (f f (* n P) (- n 1))))))

The one I did a couple of years ago had twice as many lines and was much harder to follow.

(lambda (n)
  ((lambda (fact) (fact fact 1 n))
   (lambda (f P n) (if (<= n 1) P (f f (* n P) (- n 1))))))
妄想挽回 2024-10-27 14:51:47

这是我之前在研究 Y-Combinator 时编写的代码。

[λ (n) 
    ;; Y combinator (specialized to two arguments)
    (([λ (rec-func)
        ([λ (procedure)
           (rec-func [λ (arg1 arg2) ((procedure procedure) arg1 arg2)])]
         [λ (procedure)
           (rec-func [λ (arg1 arg2) ((procedure procedure) arg1 arg2)])])]
    ;; The factorial function (tail recursive)
     [λ (func-arg)
           [λ (n tot)
             (if (zero? n)
                 tot
                 (func-arg (- n 1) (* n tot)))]]) 
     n 1)]

Here's mine that I coded up before when wrapping my head around the Y-Combinator.

[λ (n) 
    ;; Y combinator (specialized to two arguments)
    (([λ (rec-func)
        ([λ (procedure)
           (rec-func [λ (arg1 arg2) ((procedure procedure) arg1 arg2)])]
         [λ (procedure)
           (rec-func [λ (arg1 arg2) ((procedure procedure) arg1 arg2)])])]
    ;; The factorial function (tail recursive)
     [λ (func-arg)
           [λ (n tot)
             (if (zero? n)
                 tot
                 (func-arg (- n 1) (* n tot)))]]) 
     n 1)]
遥远的她 2024-10-27 14:51:47

我首先在无类型 lambda 演算中编写解决方案,使用诸如 0?truefalse 等的顶级定义,定义使用教会编码。此实现假设多参数函数是柯里化的并且函数是部分应用的(如 Haskell)。

Church 编码自然数如下:

(define 0  λf x. x)
(define 1  λf x. f x)
(define 2  λf x. f (f x))
(define 3  λf x. f (f (f x)))

Church 布尔值 truefalse 定义如下

(define const  λx y. x)
(define U      λf. f f)
(define true   λt f. t)
(define false  λt f. f)
(define succ   λn f x. f (n f x))
(define 0      λf x. x)
(define *      λm n f x. m (n f) x)
(define zero?  λn. n (const false) true)
(define pred   λn f x. n (λg h. h (g f)) (const x) id)

定义了这些先决条件后,现在我们使用自我应用程序来定义阶乘函数以进行递归。这个定义是尾递归的。

(define !
  U (lambda loop acc n.
      zero? n -- branches wrapped in lambdas
              -- to accomodate call-by-value
       (lambda _. acc)
       (lambda _. (loop loop (* n acc) (pred n))))
       n) -- dummy argument to evaluate selected branch
    1)

从这里开始,我作弊并在 ! 上执行了正常的顺序评估;这本质上是部分评估。为此,我必须删除 U 的定义以防止发散,然后将其添加回来。

这是由此产生的术语。它相当神秘(尽管如果没有翻译,我很难用手写出这么短的东西),所以我添加了注释来标识我仍然可以识别的部分。

(λx. x x)             -- self application
(λloop acc n.
  n (λy t f. f)       -- const false
    (λt f. t)         -- true
    (λ_. acc)         -- zero? branch
    (λ_. loop loop    -- other branch
      (λf. n (acc f))
      (λf x. n (λg h. h (g f)) (λy. x) (λx. x)))  -- pred
    n)  -- dummy argument
(λf. f) -- 1

乘法可能很难发现,但它确实存在。现在,为了测试它,我评估了应用于 3 的术语,即 (λf x.f (f (fx)))。混合应用和混合正态评估都减少到正常项而不发散,产生λf x。 f (f (f (f (f (fx))))),或 6。其他归约策略要么发散(由于自我应用),要么不归约到正常形式。

I first wrote the solution in the untyped lambda calculus, using top-level definitions for things like zero?, true, false, etc, defined using Church encodings. This implementation assumes that multi-argument functions are curried and that functions are partially applied (like Haskell).

Church encoding natural numbers looks like:

(define 0  λf x. x)
(define 1  λf x. f x)
(define 2  λf x. f (f x))
(define 3  λf x. f (f (f x)))

Church booleans true and false are defined below

(define const  λx y. x)
(define U      λf. f f)
(define true   λt f. t)
(define false  λt f. f)
(define succ   λn f x. f (n f x))
(define 0      λf x. x)
(define *      λm n f x. m (n f) x)
(define zero?  λn. n (const false) true)
(define pred   λn f x. n (λg h. h (g f)) (const x) id)

With those pre-requisites defined, now we define the factorial function using self-application for recursion. This definition is tail-recursive.

(define !
  U (lambda loop acc n.
      zero? n -- branches wrapped in lambdas
              -- to accomodate call-by-value
       (lambda _. acc)
       (lambda _. (loop loop (* n acc) (pred n))))
       n) -- dummy argument to evaluate selected branch
    1)

From here, I cheated and performed normal order evaluation on !; this is essentially partial evaluation. For this to work, I had to remove the definition of U to prevent divergence, then add it back in after.

Here's the resulting term. It is fairly cryptic (though I would've had difficulty writing anything this short by hand, without an interpreter) so I've added comments identifying the parts I can still recognize.

(λx. x x)             -- self application
(λloop acc n.
  n (λy t f. f)       -- const false
    (λt f. t)         -- true
    (λ_. acc)         -- zero? branch
    (λ_. loop loop    -- other branch
      (λf. n (acc f))
      (λf x. n (λg h. h (g f)) (λy. x) (λx. x)))  -- pred
    n)  -- dummy argument
(λf. f) -- 1

The multiplication might be hard to spot, but it's there. Now, to test it I evaluated the term applied to 3, or (λf x. f (f (f x))). Hybrid applicative and hybrid normal evaluation both reduce to a normal term without diverging, yielding λf x. f (f (f (f (f (f x))))), or 6. Other reduction strategies either diverge (due to the self application) or don't reduce to normal form.

迷你仙 2024-10-27 14:51:47

这是我不久前做的一个

 (define fibs
  (lambda (idx)
    ((((lambda (f)
            ((lambda (x) (x x))
             (lambda (y) (f (lambda (a)
                              (lambda (b) (((y y) a) b)))))))
       (lambda (f) (lambda (s)
                     (lambda (idx)
                       (if (= idx 0)
                           ((lambda (p)
                              (p (lambda (h) (lambda (t) h)))) s)
                           ((f (((lambda (p)
                                   (p (lambda (h) (lambda (t) t)))) s)))
                            (- idx 1)))))))
      ((((lambda (f)
            ((lambda (x) (x x))
             (lambda (y) (f (lambda (a)
                              (lambda (b) (((y y) a) b)))))))
         (lambda (f) 
           (lambda (a)
             (lambda (b)
               (((lambda (h)
                   (lambda (t) (lambda (a) ((a h) t)))) a)
                (lambda () ((f b) (+ a b)))))))) 0) 1))
     idx)))  

它定义了所有的斐波那契数(当然,通过无限的教会对列表)

我确实走得更远,摆脱了 if、0、1、+ 和 -,但在无论如何,这些都需要从教堂数字来回转换。那时它变得很荒谬。

Here's one that I did a while back

 (define fibs
  (lambda (idx)
    ((((lambda (f)
            ((lambda (x) (x x))
             (lambda (y) (f (lambda (a)
                              (lambda (b) (((y y) a) b)))))))
       (lambda (f) (lambda (s)
                     (lambda (idx)
                       (if (= idx 0)
                           ((lambda (p)
                              (p (lambda (h) (lambda (t) h)))) s)
                           ((f (((lambda (p)
                                   (p (lambda (h) (lambda (t) t)))) s)))
                            (- idx 1)))))))
      ((((lambda (f)
            ((lambda (x) (x x))
             (lambda (y) (f (lambda (a)
                              (lambda (b) (((y y) a) b)))))))
         (lambda (f) 
           (lambda (a)
             (lambda (b)
               (((lambda (h)
                   (lambda (t) (lambda (a) ((a h) t)))) a)
                (lambda () ((f b) (+ a b)))))))) 0) 1))
     idx)))  

It defines all of the fibonacci numbers (via a infinite list of church pairs, of course)

I did go even further, getting rid of if, 0, 1, +, and -, but in the end those were needed to convert back and forth from church numerals anyway. And it was getting ridiculous at that point.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文