[英]Finite number of recursions in Javascript with ES6 Y-combinator
I came across an answer to another SO question about recursion in Javascript, that gave a very terse form in ES6 using the Y-combinator, using ES6's fat arrow, and thought hey, that'd be neat to use - then 15 minutes or so later had circled back to hm, maybe not . 我碰到一个答案,大约在Javascript递归另一个SO问题,即使用的Y组合子,使用ES6的脂肪箭头给了一个非常简洁的形式在ES6,心想嘿,这会是整齐的使用 -然后在15分钟左右后来回过头去,也许不是 。
I've been to a few Haskell/Idris talks and run some code before, and familiar with standard JS, so was hoping I'd be able to figure this out, but can't quite see how a simple "do n
recursions and return" is supposed to go, and where to implement a decrementing counter. 我去过几个哈斯克尔/伊德里斯会谈之前运行一些代码,并熟悉标准JS,所以希望我能想出解决办法,但不能完全看到一个简单的“做
n
递归和返回”,以及在何处实施减量计数器。
I just wanted to simplify getting the n
th parent node of a DOM element, and there seem to be more dense explain-all guides than examples of simple applications like this. 我只是想简化获取DOM元素的第
n
个父节点的工作,并且似乎有比此类简单应用程序示例更密集的解释所有指南。
The example I first saw is: 我首先看到的示例是:
const Y = a => (a => a(a))(b => a(a => b(b)(a)));
while this more recent answer gives: 而这个最新答案给出了:
const U = f => f (f)
const Y = U (h => f => f (x => h(h)(f)(x)))
...which is given with examples of what the internal functions might be, and some example outputs, but introducing the U-combinator doesn't really help clarify this for me. ...给出了可能是内部函数的示例以及一些示例输出,但是引入U-combinator并没有真正帮助我澄清这一点。
In the first example I can't really begin to understand what b
might be in my case - I know I need one function a
to return the parent node: 在第一个示例中,我无法真正开始理解
b
含义-我知道我需要一个函数a
返回父节点:
const par = function(node) {
return node.parentNode;
}
I came up with the below: 我提出以下内容:
function RecParentNode(base_node, n) {
// ES6 Y-combinator, via: https://stackoverflow.com/a/32851197/2668831
// define immutable [const] functions `Y` and `fn` [`fn` uses `Y`]
// the arguments of `Y` are another two functions, `a` and `b`
const Y = par=>(par=>par(par))(b=>par(par=>b(b)(par)));
const fn = Y(fn => n => {
console.log(n);
if (n > 0) {
fn(n - 1);
}
});
}
but then couldn't see what to do with the spare b
lying around, and was about to delete it all and just forget I bothered. 但是后来看不到备用
b
怎么办,打算将其全部删除,而忘了我打扰了。
All I'd like is to apply the par
function n
times, since the only alternatives I'm aware of are chaining .parentNode.parentNode.parentNode
... or cheating and turning a string into an eval
call. 我只想将
par
函数应用n
次,因为我知道的唯一选择是链接.parentNode.parentNode.parentNode
...或作弊并将字符串转换为eval
调用。
Hoping someone familiar with functional JS could help me get the idea here for how to use the Y-combinator to make this helper function RecParentNode
- thanks! 希望熟悉函数式JS的人可以帮助我在这里获得如何使用Y组合器使该辅助函数
RecParentNode
-谢谢!
due diligence 尽职调查
Hey, that answer you found is mine! 嘿,您找到的答案是我的! But before looking at various definitions of the Y combinator, we first review its purpose: (emphasis mine)
但是在查看Y组合器的各种定义之前,我们首先回顾一下其目的:(强调我的)
In functional programming, the Y combinator can be used to formally define recursive functions in a programming language that doesn't support recursion ( wikipedia )
在函数式编程中,可以使用Y组合器以不支持递归的编程语言形式正式定义递归函数( Wikipedia )
Now, let's review your question 现在,让我们回顾您的问题
I just wanted to simplify getting the nth parent node of a DOM element, and there seem to be more dense explain-all guides than examples of simple applications like this.
我只是想简化获取DOM元素的第n个父节点的工作,并且似乎有比此类简单应用程序示例更密集的解释所有指南。
JavaScript supports direct recursion which means functions can call themselves directly by name. JavaScript支持直接递归,这意味着函数可以通过名称直接调用自身。 No use of U or Y combinators is necessary.
无需使用U或Y组合器。 Now to design a recursive function, we need to identify our base and inductive case(s)
现在要设计一个递归函数,我们需要确定基本情况和归纳情况
n
is zero; n
为零; return the node
node
n
is not zero, but node
is empty; n
不为零,但node
为空; we cannot get the parent of an empty node; undefined
(or some error if you wish) undefined
(如果需要,则返回一些错误) n
is not zero and node
is not empty; n
不为零且node
不为空; recur using the node's parent and decrement n
by 1. n
1。 Below we write nthParent
as a pure functional expression. 下面我们将
nthParent
编写为纯函数表达式。 To simplify the discussion to follow, we will define it function in curried form . 为了简化随后的讨论,我们将以咖喱形式定义它的功能。
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 )
but what if... 但是如果...
So suppose you were running your program with a JavaScript interpreter that did not support direct recursion... now you have a use case for the combinators 因此,假设您正在使用不支持直接递归的JavaScript解释器运行程序... 现在您有了组合器的用例
To remove the call-by-name recursion, we wrap our entire function in another lambda whose parameter f
(or name of your choosing) will be the recursion mechanism itself. 要删除“按名称调用”递归,我们将整个函数包装在另一个lambda中,其参数
f
(或您选择的名称)将是递归机制本身。 It is a drop-in replacement for nthParent
– changes in bold 它是
nthParent
替代nthParent
- 粗体更改
const nthParent =
Y (f => (node = Empty) => (n = 0) =>
n === 0
? node
: node === Empty
? undefined
: nthParent
f (node.parentNode) (n - 1)
)
Now we can define Y 现在我们可以定义Y
const Y = f =>
f (Y (f))
And we can remove direct recursion in Y with U using a similar technique as before– changes in bold 而且,我们可以使用与以前类似的技术,使用U删除Y中的直接递归- 粗体更改
const U = f =>
f (f)
const Y =
U (g => f =>
f (Y
U (g) (f))
)
But in order for it to work in JavaScript, which uses applicative order evaluation , we must delay evaluation using eta expansion – changes in bold 但是,为了使其能够在使用应用顺序评估的 JavaScript中工作,我们必须延迟使用eta扩展进行评估- 粗体更改
const U = f =>
f (f)
const Y = U (g => f =>
f (
x => U (g) (f)
(x)))
All together now 现在都在一起了
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 )
Now I hope you see why the Y combinator exists and why you wouldn't use it in JavaScript. 现在,我希望您了解为什么Y组合器存在以及为什么不在JavaScript中使用它。 In another answer, I attempt to help readers gain a deeper intuition about the Y combinator by use of a mirror analogy .
在另一个答案中,我试图通过使用镜像类比帮助读者更深入地了解Y组合器。 I invite you to read it if the topic interests you.
如果您感兴趣,我邀请您阅读。
getting practical 变得实用
It doesn't make sense to use the Y combinator when JavaScript already supports direct recursion. 当JavaScript已经支持直接递归时,使用Y组合器是没有意义的。 Below, see a more practical definition of
nthParent
in uncurried form 下面,以
nthParent
修改的形式查看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)
But what about those maximum recursion depth stack overflow errors? 但是那些最大递归深度堆栈溢出错误呢? If we had a deep tree with nodes thousands of levels deep, the above function will produce such an error.
如果我们有一棵深树,节点数以千计,那么上面的函数将产生这样的错误。 In this answer I introduce several ways to work around the problem.
在此答案中,我介绍了解决此问题的几种方法。 It is possible to write stack-safe recursive functions in a language that doesn't support direct recursion and/or tail call elimination !
它是可以编写堆栈安全递归函数中不支持直接递归和/或语言尾部调用消除 !
If imperative programming is an option: 如果命令式编程是一种选择:
function getParent(el, n){
while(n--) el = el.parentNode;
return el;
}
Using functional recursion you could do: 使用函数递归可以做到:
const Y = f => x => f (Y (f)) (x); // thanks to @Naomik
const getParent = Y(f => el => n => n ? f(el.parentNode)(n - 1) : el);
console.log(getParent(document.getElementById("test"))(5));
Lets build this Y-Combinator from the ground up. 让我们从头开始构建这个Y组合器。 As it calls a function by the Y-Combinator of itself, the Y-Combinator needs a reference to itself.
当它由自身的Y组合器调用函数时,Y组合器需要对其自身的引用。 For that we need a U-Combinator first:
为此,我们首先需要一个U-Combinator:
(U => U(U))
Now we can call that U combinator with our Y combinator so that it gets a self reference: 现在我们可以将U组合器与Y组合器一起调用,以便获得自引用:
(U => U(U))
(Y => f => f( Y(Y)(f) ))
However that has a problem: The function gets called with an Y-Combinator reference which gets called with a Y-Combinator reference wich gets called .... weve got Infinite recursion. 但是,这有一个问题:该函数使用Y-Combinator引用进行调用,而Y-Combinator引用进行调用,而....则被称为无限递归。 Naomik outlined that here .
Naomik在这里概述了这一点 。 The solution for that is to add another curried argument(eg
x
) that gets called when the function is used, and then another recursive combinator is created. 解决方案是添加使用该函数时调用的另一个咖喱参数(例如
x
),然后创建另一个递归组合器。 So we only get the amount of recursion we actually need: 因此,我们仅获得实际需要的递归数量:
(U => U(U))
(Y => f => x => f( Y(Y)(f) )(x) )
(f => n => n ? f(n - 1): n)(10) // a small example
We could also restructure it like this: 我们也可以像这样重组它:
(f => (U => U(U))(Y => f(x => Y(Y)(x))))
(f => n => n ? f(n - 1): n)(10) // a small example
To get your first snippet, so basically its the same thing just a bit reordered and obfuscated through shadowing. 为了获得您的第一个摘录,基本上相同的事情只是通过阴影进行了重新排序和混淆。
So now another combinator gets only created when f(n-1)
gets called, which only happens when n?
因此,现在只有在调用
f(n-1)
时才创建另一个组合器,这仅在n?
时发生n?
, so weve got an exit condition now. ,因此我们现在有了退出条件。 Now we can finally add our node to the whole thing:
现在,我们终于可以将节点添加到整个对象中:
(U => U(U))
(Y => f => x => f( Y(Y)(f) )(x) )
(f => el => n => n ? f(el.parentNode)(n - 1): el)
(document.getElementById("test"))(10)
That would be purely functional , however that is not really useful as it is extremely complicated to use. 那将纯粹是功能性的 ,但是由于使用起来极其复杂,因此并没有真正的用处。 If we store function references we dont need the U combinator as we can simply take the Y reference.
如果我们存储函数引用,则不需要U组合器,因为我们可以简单地采用Y引用。 Then we arrive at the snippet above.
然后我们到达上面的代码段。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.