用纯lambda演算和Racket中的Church数实现Fibonacci序列

用纯lambda演算和Racket中的Church数实现Fibonacci序列,lambda,racket,fibonacci,lambda-calculus,Lambda,Racket,Fibonacci,Lambda Calculus,我已经和Lambda演算斗争了很长一段时间了。有很多资源解释如何减少嵌套的lambda表达式,但很少有资源指导我编写自己的lambda表达式 我正在尝试使用纯lambda演算(即单参数函数、Church数字)编写Racket中的递归斐波那契解 以下是我一直使用的教堂数字的定义: (define zero (λ (f) (λ (x) x))) (define one (λ (f) (λ (x) (f x)))) (define two (λ (f) (λ (x) (f (f x)))))

我已经和Lambda演算斗争了很长一段时间了。有很多资源解释如何减少嵌套的lambda表达式,但很少有资源指导我编写自己的lambda表达式

我正在尝试使用纯lambda演算(即单参数函数、Church数字)编写Racket中的递归斐波那契解

以下是我一直使用的教堂数字的定义:

(define zero  (λ (f) (λ (x) x)))
(define one   (λ (f) (λ (x) (f x))))
(define two   (λ (f) (λ (x) (f (f x)))))
(define three (λ (f) (λ (x) (f (f (f x))))))
(define four  (λ (f) (λ (x) (f (f (f (f x)))))))
(define five  (λ (f) (λ (x) (f (f (f (f (f x))))))))
(define six   (λ (f) (λ (x) (f (f (f (f (f (f x)))))))))
(define seven (λ (f) (λ (x) (f (f (f (f (f (f (f x))))))))))
(define eight (λ (f) (λ (x) (f (f (f (f (f (f (f (f x)))))))))))
(define nine  (λ (f) (λ (x) (f (f (f (f (f (f (f (f (f x))))))))))))
这些是我尝试合并的单参数函数:

(define succ   (λ (n) (λ (f) (λ (x) (f ((n f) x))))))
(define plus   (λ (n) (λ (m) ((m succ) n))))
(define mult   (λ (n) (λ (m) ((m (plus n)) zero))))
(define TRUE   (λ (t) (λ (f) t)))
(define FALSE  (λ (t) (λ (f) f)))
(define COND   (λ (c) (λ (x) (λ (y) ((c x) y)))))
(define iszero (λ (x) (x ((λ (y) FALSE) TRUE))))
(define pair   (λ (m) (λ (n) (λ (b) (((IF b) m) n)))))
(define fst    (λ (p) (p TRUE)))
(define snd    (λ (p) (p FALSE)))
(define pzero  ((pair zero) zero))
(define psucc  (λ (n) ((pair (snd n)) (succ (snd n)))))
(define pred   (λ (n) (λ (f) (λ (x) (((n (λ (g) (λ (h) (h (g f))))) (λ (u) x))(λ (u) u))))))
(define sub    (λ (m) (λ (n) ((n pred) m))))
(define leq    (λ (m) (λ (n) (iszero ((sub m) n))))) ;; less than or equal
(define Y      ((λ (f) (f f)) (λ (z) (λ (f) (f (λ (x) (((z z) f) x))))))) ;; Y combinator
我从在Racket中编写递归斐波那契开始:

(define (fib depth)
  (if (> depth 1)
    (+ (fib (- depth 1)) (fib (- depth 2)))
  depth))
但在我的多次尝试中,我都没有成功地使用纯lambda演算来编写它。甚至开始都是一场斗争

(define fib
   (λ (x) ((leq x) one)))
我称之为(例如):

这至少是可行的(正确地返回0或1),但如果添加任何超出此范围的内容,则会破坏一切

我对球拍非常缺乏经验,而Lambda演算对于一个直到最近才开始学习的人来说是一个相当令人头疼的问题

我想了解如何构建这个函数,并将递归与Y组合器结合起来。我特别希望在任何代码旁边有一个解释。让它与
fib(zero)
一起工作到
fib(six)
就足够了,因为我可以担心以后会扩展教会的定义


编辑:

我的
iszero
函数在我的实现中是一个隐藏的破坏者。这是一个正确的版本,包含Alex答案中更新的布尔值:

(define iszero (λ (x) ((x (λ (y) FALSE)) TRUE)))
(define TRUE   (λ (t) (λ (f) (t))))
(define FALSE  (λ (t) (λ (f) (f))))
有了这些变化,并结合了thunks,一切都正常工作

分支形式与短路 如果您使用的是像Racket这样渴望(而不是懒惰)的语言,那么您需要注意如何对分支形式(如
COND
函数)进行编码

您现有的布尔和条件词定义如下:

(define TRUE   (λ (t) (λ (f) t)))
(define FALSE  (λ (t) (λ (f) f)))
(define COND   (λ (c) (λ (x) (λ (y) ((c x) y)))))
它们适用于以下简单情况:

> (((COND TRUE) "yes") "no")
"yes"
> (((COND FALSE) "yes") "no")
"no"
(((COND TRUE) (λ () "yes")) (λ () (error "doesn't get here")))
然而,如果“未执行分支”将产生错误或无限循环,那么好的分支形式将“短路”以避免触发它。一个好的分支表单应该只评估它需要执行的分支

> (if #true "yes" (error "shouldn't get here"))
"yes"
> (if #false (error "shouldn't trigger this either") "no")
"no"
但是,您的
COND
会计算两个分支,这仅仅是因为Racket的函数应用程序会计算所有参数:

> (((COND TRUE) "yes") (error "shouldn't get here"))
;shouldn't get here
> (((COND FALSE) (error "shouldn't trigger this either")) "no")
;shouldn't trigger this either
使用额外的lambda实现短路 我被教导用一种渴望的语言来解决这个问题(例如,不切换到
#lang lazy
)的方法是将thunk传递到如下分支形式:

> (((COND TRUE) "yes") "no")
"yes"
> (((COND FALSE) "yes") "no")
"no"
(((COND TRUE) (λ () "yes")) (λ () (error "doesn't get here")))
然而,这需要对布尔值的定义稍作调整。在此之前,布尔值从两个值中进行选择,并返回一个值。现在,一个布尔值将有两个thunk可供选择,它将调用一个thunk

COND
表单的定义方式与以前相同,但您必须以不同的方式使用它。要翻译
(如果是c t e)
,请在编写之前:

(((COND c) t) e)
现在有了布尔的新定义,你可以写:

(((COND c) (λ () t)) (λ () e))
我将把
(λ()expr)
缩写为
{expr}
,这样我就可以这样写了:

(((COND c) {t}) {e})
现在,先前因错误而失败的操作将返回正确的结果:

> (((COND TRUE) {"yes"}) {(error "shouldn't get here")})
"yes"
这允许您编写条件语句,其中一个分支是停止的“基本情况”,另一个分支是继续进行的“递归情况”

(Y (λ (fib)
     (λ (x)
       (((COND ((leq x) one))
         {x})
        {... (fib (sub x two)) ...}))))

如果没有那些额外的
(λ()…)
,没有布尔的新定义,这将永远循环,因为Racket急切(而不是懒惰)的参数求值。

我非常感谢这里的完整回答。你解释了一个我不知道的关于球拍的重要细微差别。不过,我未能成功地实施你的建议。似乎还没有正确使用短路评估。我正在编辑我的问题,以反映变化和我的最新尝试。这里还有一个后续问题:你的建议实际上是我对球拍理解中缺失的环节。然而,我的
iszero
功能有一个问题,这是无法解释的。一旦我修改了布尔函数以运行函数而不是返回项,将thunk合并到条件分支中,并修复了
为零的问题,它的工作就像一个符咒。非常感谢。