尽职调查
嘿,你找到的答案是我的!但是在查看Y组合器的各种定义之前,我们首先回顾一下它的目的:(强调我的)
在函数式编程中,Y 组合器可用于在不支持递归的编程语言中正式定义递归函数(维基百科)
现在,让我们回顾一下您的问题
我只是想简化获取 DOM 元素的第 n 个父节点,并且似乎比这样的简单应用程序示例更密集的解释所有指南。
JavaScript 支持直接递归,这意味着函数可以通过名称直接调用自己。不需要使用U或Y组合子。现在要设计一个递归函数,我们需要确定我们的基础和归纳案例
- 基本情况——
n
为零;返回node
- 归纳案例 1 –
n
不是零,而是空node
的;我们无法获得空节点的父节点;返回undefined
(或一些错误,如果你愿意)
- 归纳案例 2 -
n
不为零node
且不为空;使用节点的父节点递归并减n
1。
下面我们写成nthParent
纯函数表达式。为了简化后面的讨论,我们将它定义为柯里化形式的函数。
const Empty =
Symbol ()
const nthParent = (node = Empty) => (n = 0) =>
n === 0
? node
: node === Empty
? undefined // or some kind of error; this node does not have a parent
: nthParent (node.parentNode) (n - 1)
const Node = (value = null, parentNode = Empty) =>
({ Node, value, parentNode })
const data =
Node (5, Node (4, Node (3, Node (2, Node (1)))))
console.log
( nthParent (data) (1) .value // 4
, nthParent (data) (2) .value // 3
, nthParent (data) (3) .value // 2
, nthParent (data) (6) // undefined
)
但如果...
因此,假设您使用不支持直接递归的 JavaScript 解释器运行程序……现在您有了组合子的用例
为了消除按名称调用的递归,我们将整个函数包装在另一个 lambda 中,其参数f
(或您选择的名称)将是递归机制本身。它是替换nthParent
-粗体的变化
const nthParent = Y (f => (node = Empty) => (n = 0) =>
n === 0
? node
: node === Empty
? undefined
: nthParent f (node.parentNode) (n - 1))
现在我们可以定义Y
const Y = f =>
f (Y (f))
我们可以使用与以前类似的技术来删除Y和U中的直接递归——粗体的变化
const U = f =>
f (f)
const Y = U (g => f =>
f (Y U (g) (f)))
但是为了让它在使用应用顺序评估的 JavaScript 中工作,我们必须使用eta 扩展延迟评估- 更改为粗体
const U = f =>
f (f)
const Y = U (g => f =>
f (x => U (g) (f) (x)))
现在都在一起了
const U = f =>
f (f)
const Y = U (g => f =>
f (x => U (g) (f) (x)))
const Empty =
Symbol ()
const nthParent = Y (f => (node = Empty) => (n = 0) =>
n === 0
? node
: node === Empty
? undefined // or some kind of error; this node does not have a parent
: f (node.parentNode) (n - 1))
const Node = (value = null, parentNode = Empty) =>
({ Node, value, parentNode })
const data =
Node (5, Node (4, Node (3, Node (2, Node (1)))))
console.log
( nthParent (data) (1) .value // 4
, nthParent (data) (2) .value // 3
, nthParent (data) (3) .value // 2
, nthParent (data) (6) // undefined
)
现在我希望你明白为什么 Y 组合器存在以及为什么你不会在 JavaScript 中使用它。在另一个答案中,我试图通过使用镜像类比来帮助读者更深入地了解 Y 组合器。如果您对主题感兴趣,我邀请您阅读它。
变得实用
当 JavaScript 已经支持直接递归时,使用 Y 组合子没有意义。下面,看一个更实用的nthParent
非咖喱形式的定义
const nthParent = (node = Empty, n = 0) =>
n === 0
? node
: node === Empty
? undefined // or some kind of error; this node does not have a parent
: nthParent (node.parentNode, n - 1)
但是那些最大递归深度堆栈溢出错误呢?如果我们有一棵具有数千层深的节点的树,上面的函数就会产生这样的错误。在这个答案中,我介绍了几种解决问题的方法。可以用不支持直接递归和/或尾调用消除的语言编写堆栈安全递归函数!