如何使用 Clojure 语言的子集在 lambda 演算中实现递归函数?

2023-12-30

我正在阅读 Greg Michaelson 所著的《通过 Lambda 演算进行函数式编程简介》一书来学习 lambda 演算。

我仅使用 Clojure 语言的一个子集来实​​现示例。我只允许:

  • symbols
  • 单参数 lambda 函数
  • 功能应用
  • var 定义是为了方便。

到目前为止,我已经使这些功能正常工作:

(def identity (fn [x] x))
(def self-application (fn [s] (s s)))

(def select-first (fn [first] (fn [second] first)))
(def select-second (fn [first] (fn [second] second)))
(def make-pair (fn [first] (fn [second] (fn [func] ((func first) second)))))    ;; def make-pair =  λfirst.λsecond.λfunc.((func first) second)

(def cond make-pair)
(def True select-first)
(def False select-second)

(def zero identity)
(def succ (fn [n-1] (fn [s] ((s False) n-1))))
(def one (succ zero))
(def zero? (fn [n] (n select-first)))
(def pred (fn [n] (((zero? n) zero) (n select-second))))

但现在我陷入了递归函数的困境。更准确地说是关于实施add。书中提到的第一个尝试是这样的:

(def add-1
  (fn [a]
    (fn [b]
      (((cond a) ((add-1 (succ a)) (pred b))) (zero? b)))))

((add zero) zero)

减少力的 Lambda 演算规则取代了内部调用add-1与包含定义本身的实际定义......无休止。

在 Clojure(一种应用程序订单语言)中,add-1在任何类型的执行之前也会急切地评估,我们得到了StackOverflowError.

经过一番摸索后,本书提出了一种巧妙的设计,用于避免前面示例的无限替换。

(def add2 (fn [f]
            (fn [a]
              (fn [b]
                (((zero? b) a) (((f f) (succ a)) (pred b)))))))
(def add (add2 add2))

的定义add扩展到

(def add (fn [a] 
           (fn [b]
             (((zero? b) a) (((add2 add2) (succ a)) (pred b)))))) 

在我们尝试之前这完全没问题!这就是 Clojure 要做的事情(引用透明度):

((add zero) zero)
;; ~=>
(((zero? zero) zero) (((add2 add2) (succ zero)) (pred zero)))
;; ~=>
((select-first zero) (((add2 add2) (succ zero)) (pred zero)))
;; ~=>
((fn [second] zero) ((add (succ zero)) (pred zero)))

在最后一行(fn [second] zero)是一个 lambda,在应用时需要一个参数。这里的论据是((add (succ zero)) (pred zero))。 Clojure 是一种“应用顺序”语言,因此参数在函数应用之前进行评估,即使在这种情况下根本不会使用参数。在这里我们再次出现add会再次出现在add...直到堆栈爆炸。 在像 Haskell 这样的语言中,我认为这很好,因为它很懒(正常顺序),但我使用的是 Clojure。

之后,这本书详细介绍了避免样板文件的美味 Y 组合器,但我得出了同样可怕的结论。

EDIT

正如 @amalloy 所建议的,我定义了 Z 组合器:

(def YC (fn [f] ((fn [x] (f (fn [z] ((x x) z)))) (fn [x] (f (fn [z] ((x x) z)))))))

我定义了add2像这样:

(def add2 (fn [f]
            (fn [a]
              (fn [b]
                (((zero? b) a) ((f (succ a)) (pred b)))))))

我这样使用它:

(((YC add2) zero) zero)

但我仍然得到了 StackOverflow。

我尝试“手动”扩展该功能,但经过 5 轮 beta 缩减后,它看起来像是在括号森林中无限扩展。

那么有什么技巧可以让 Clojure 在没有宏的情况下成为“正常顺序”而不是“应用顺序”。有可能吗?这甚至可以解决我的问题吗?

这个问题与这个问题非常接近:如何使用scheme lisp实现lambda演算的迭代? https://stackoverflow.com/questions/45931279/how-to-implement-iteration-of-lambda-calculus-using-scheme-lisp。只是我的内容是关于 Clojure 而不一定是关于 Y-Combinator。


对于严格的语言,您需要Z组合器 https://en.wikipedia.org/wiki/Fixed-point_combinator#Strict_fixed_point_combinator而不是 Y 组合器。基本思想相同,但替换为(x x) with (fn [v] (x x) v)这样自引用就被封装在 lambda 中,这意味着只有在需要时才会对其进行求值。

您还需要修复布尔值的定义,以便使它们以严格的语言工作:您不能只向其传递您关心的两个值并在它们之间进行选择。相反,您传递它 thunk 来计算您关心的两个值,并使用虚拟参数调用适当的函数。也就是说,就像您通过 eta 扩展递归调用来修复 Y 组合器一样,您也可以通过 eta 扩展 if 和 eta-reduce 布尔值本身的两个分支来修复布尔值(我不能 100% 确定 eta-reducing这里是正确的术语)。

(def add2 (fn [f]
            (fn [a]
              (fn [b]
                ((((zero? b) (fn [_] a)) (fn [_] ((f (succ a)) (pred b)))) b)))))

请注意,if 的两个分支现在都用(fn [_] ...),并且 if 本身被包裹着(... b),其中b是我任意选择传入的值;你可以通过zero相反,或者任何东西。

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

如何使用 Clojure 语言的子集在 lambda 演算中实现递归函数? 的相关文章

  • php 删除特定文件夹及其所有内容

    我正在使用 php 删除包含已删除帖子图像的文件夹 我正在使用下面的代码 这是我在网上找到的并且做得很好 我想知道当一个文件夹中有其他文件夹时 如何只删除其中的特定文件夹 当我使用下面的代码时 如何才能做到这一点 使用 dev images
  • Karasuba算法递归过多

    我正在尝试用 c 实现 Karasuba 乘法算法 但现在我只是想让它在 python 中工作 这是我的代码 def mult x y b m if max x y lt b return x y bm pow b m x0 x bm x1
  • 在 Clojure 和其他 Lisp 方言中,在函数名称末尾使用星号的约定是什么?

    请注意 我不是在谈论符号名称中的耳罩 这个问题在Clojure 常量的约定 样式和用法 https stackoverflow com questions 3579063 conventions style and usage for cl
  • 通往楼梯顶部的可能路径

    这是一个非常经典的问题 我听说谷歌在他们的面试中使用过这个问题 问题 制定一个递归方法 打印从楼梯底部到楼梯顶部的所有可能的独特路径 有 n 个楼梯 您一次只能走 1 步或 2 步 示例输出 如果它是一个有 3 级楼梯的楼梯 1 1 1 2
  • 为什么Racket中foldl的定义方式很奇怪?

    在 Haskell 中 与许多其他函数式语言一样 函数foldl被定义为 例如 foldl 0 1 2 3 4 10 这没关系 因为foldl 0 1 2 3 4 根据定义 0 1 2 3 4 但是 在 球拍 中 foldl 0 1 2 3
  • Javascript - deepEqual 比较

    问题 来自 Eloquent Javascript 第二版 第 4 章 练习 4 编写一个函数 deepEqual 它接受两个值 并且仅当它们相等时才返回 true 是相同的值或具有相同属性的对象 其值也是 与对 deepEqual 的递归
  • C# 中的递归自定义配置

    我正在尝试创建一个遵循以下递归结构的自定义配置部分
  • 我在函数的最后一次递归调用中得到“方案应用程序而不是过程”

    所以这是代码 define time prime test n newline display n start prime test n runtime define start prime test n start time if pri
  • 将数据库与 Clojure 结合使用

    有哪些使用 Clojure 数据库的方法 我从 Clojure 知道你可以用 Java 做任何事情 但这意味着我最终可能会使用一些过于复杂的东西 比如 Hibernate 这与 Clojure 的简单性相冲突 有什么建议或意见吗 Cloju
  • 如何编写 Clojure 宏来从字符串创建正则表达式?

    我正在创建一个方便的宏 部分便利在于可以仅使用字符串来指定正则表达式 而不是使用 re 表示法 我无法弄清楚的一部分是如何让宏获取字符串并将其重写为 Clojure 正则表达式 例如 生成 re 符号 我认为这是一个语法 转义问题 我的第一
  • 来自 JSON 的 Angular 8 动态表单

    我正在尝试从 JSON 模式递归生成动态表单 但我正在努力解决找不到表单控件的问题 这是代码示例 我收到这个错误 错误错误 找不到名称为 createdAt 的控件 我尝试了不同的方法 但仍然存在问题 我知道我错过了一些东西 所以请帮忙 任
  • 宏内调用函数和宏的区别?

    我的难题是以下示例 defmacro macro1 x println x defn func1 x println x defmacro macro2 x macro1 x func1 x defmacro macro3 x func1
  • Clojure/Ring:使用环码头适配器,大请求会给我一个 413: FULL HEAD 错误。

    使用 Ring 的 Jetty 适配器 如果我的请求太大 我会收到 413 FULL HEAD 错误 我追踪到一个名为 headerbuffersize 的属性 但是当我尝试在 run jetty 调用中设置它时 我仍然得到 413 有没有
  • 函数式 Scala 中的选择排序

    我正在学习 Scala 编程 并编写了选择排序算法的快速实现 然而 由于我对函数式编程还不太了解 所以在转换为更 Scala 风格时遇到了困难 对于 Scala 程序员来说 如何使用 Lists 和 vals 来做到这一点 而不是回到我的命
  • Java 递归和性能

    递归对处理器和内存的影响是否很大 我的意思是 我的一个线程有一个方法 很可能会调用自身 假设它每秒可以自调用一次 我的应用程序应该运行至少 24 小时而不停止 因此它提供了 60 60 24 86400 个自调用方法 它对第二个 主 线程有
  • 如何使用递归查找数字中的最小元素 [C]

    好的 所以我正在准备我的 C 考试 当谈到递归时我有点卡住了我是大学一年级的学生 这对我来说似乎有点困难 练习要求在给定的数字中使用递归函数我需要找到最小的元素 例如 52873 是 2 程序需要打印 2 include
  • 确保 Clojure 中只有一个服务实例正在运行/启动/停止的规范方法?

    我正在用 Neo4j 支持的 Clojure 编写一个有状态服务器 它可以服务套接字请求 例如 HTTP 当然 这意味着我需要能够从该服务器内启动和停止套接字服务器 在设计方面 我希望能够在此服务器中声明一个 服务 并启动和停止它 我在 C
  • 通过递归扩展 Prolog 目标?

    我 最终 实现了一些目标 这些目标将根据开始由 开始之后 and duration 然而 计划目标仅接受规定数量的任务 我想扩展计划目标的功能以接受单个列表并在计划时迭代该列表 不幸的是 我认为这将需要与can run and 冲突目标如下
  • 递归树遍历 - 如何跟踪递归级别?

    我基本上试图从表示树结构的多维数组构建 html ul li 嵌套列表 下面的代码工作正常 但我想改进它 我需要一种方法来跟踪递归级别 以便我可以将不同的类应用于不同的级别 向生成的输出添加缩进等 function buildTree tr
  • 递归:n项级数之和

    需要递归函数 系列是 1 2 3 3 4 5 4 5 6 7 递归求 n 的级数之和 我无法想到应该在函数中传递哪些参数 我的方法 我认为我应该传递 n 要相乘的项数 但我无法想到的是我应该如何在同一个函数中 和 以及我的 return 语

随机推荐