[英]Crockford's Javascript Applicative Order Y Combinator syntax construct explanation?
想了解语法构造的作用/查找涵盖该主题的适当文档。
正在https://www.crockford.com/little.html上阅读Crockford的The Little JavaScripter。
我随意地重新格式化了代码,以指出我感到困惑的地方。
注意:错误地格式化这段代码太容易了,解析器插入会妨碍执行并呈现阶乘无效功能(也许值得提出新的问题)
function Y(le) {
// just one return
return (
// ok, so far it returns a function
function (f) { return f(f); }
// here it starts to be syntaxically puzzling
// what does following part, after already returning
// the function do?
// construct looks like 'return ( f1(x) (f2 (x)) )'...
// looks very lispy
(function (f) {
return le(function (x) {
return f(f)(x);
} );
} )
);
}
var factorial = Y(function (fac) {
return function (n) {
return (
n <= 2
? n
: n * fac(n - 1)
);
};
});
var number120 = factorial(5);
document.write(number120);
我知道所谓的IIFE语法构造https://developer.mozilla.org/en-US/docs/Glossary/IIFE,但此处使用的构造并没有真正遵循IIFE(据我了解IIFE)。 这是一种颠倒的构造,看起来像是最近引入JavaScript的其他某种函数构造,但是我找不到涵盖此用途的文档部分。 是否有一个术语涵盖该构造,就像有IIFE一样(因此我可以按该术语进行搜索)。
欢迎链接到文档。
这是一个IIFE。 如果我们将其格式设置为:
return (function (f) { return f(f); })(
function (f) {
return le(function (x) {
return f(f)(x);
}
}
});
我的困惑是由于不了解:
var x = (function (a) {return a})(1);
var y = (function (a) {return a} (2));
var z = function (a) {return a} (3);
是同一类型的所有IIFE变体。 至少以相同的方式(在现代浏览器中)识别并执行所有3个。 MDN需要澄清这一点。
Crockford的示例使用变量y。
与其对Crockford的Y
进行逆向工程,不如先从其目的开始理解Y
更好。 当我们从一个意图开始并朝着解决方案前进的道路时,我们就可以开始理解Y
工作原理。
“我想写一个递归公式...而不使用递归”
在人们广泛使用计算机之前,这是数学家和逻辑学家面临的问题。 这就是Y
组合器要解决的问题,但是它是如何做到的呢? 让我们首先从我们要编写的递归“公式”开始-
const fact = n =>
n <= 2
? n
: n * fact (n - 1) // <-- recursive!
以上fact
是根据自身定义的。 我们可以通过使用额外的参数来控制递归来删除此自引用, recur
const fact = n =>
const fact = recur => n =>
n <= 2
? n
: n * fact (n - 1)
: n * ... (n - 1)
但是应该recur
什么呢? 以及我们如何填写...
? 认识U
U
简单地将一个函数应用于自身-
const U = f => f (f) // <-- apply f to itself const fact = recur => n => n <= 2 ? n : n * U (recur) (n - 1) // <-- apply recur to itself console.log (U (fact) (5)) // <-- apply fact to itself // 120
在上面,我们称U (fact) (5)
来计算结果。 重要的是要注意,当我们定义fact = ...
时,我们可以应用U
const U = f => f (f) const fact = U (recur => n => // <-- apply U directly on lambda n <= 2 ? n : n * U (recur) (n - 1)) console.log (fact (5)) // <-- call fact normally // 120
使用U
,我们现在有一种通用的方式来编写递归表达式-
U (recur => ... U (recur) ...)
“我不喜欢这样,我必须记住使用U (recur) (...)
。我可以只调用recur (...)
吗?”
U
是一个非常简单的机制,但确实有点麻烦,因为我们必须记住每次recur
将“重新反射”回自身。 这就是Y
组合器与U
不同之处。 Y
说,
f
,要求递归机制 x => ...
,当您调用它时,我将处理将x
传递给某些U (recur) (...)
递归U (recur) (...)
const Y = f =>
f (x => ...) // <-- instead of f (f)...
就像上面看到的fact
,我们可以使用U
编写递归表达式
U (recur => ... U (recur) ...)
因此,某些函数f
Y
将创建递归机制U (recur => ...)
,它将使用函数x => ...
调用f
,该函数在应用时将使用U (recur) (x)
使用x
递归U (recur) (x)
-
const Y = f =>
// f (x => ... (x))
// ======= ====
// | |
// v v
// U (recur => ... U (recur) ...)
U (recur => f (x => U (recur) (x)))
让我们在下面看到Y
的作用-
const U = f => f (f) const Y = f => U (recur => f (x => U (recur) (x))) const fact = Y (recur => n => n <= 2 ? n : n * recur (n - 1)) // <-- call "recur" normally console.log (fact (5)) // <-- call "fact" normally // 120
番茄,番茄
克罗克福德将Y
组合器称为“ ...计算机科学中最奇怪,最奇妙的工件之一” ,但是我认为这是因为他不理解它,并且继续复制/粘贴您在互联网上看到的相同的Y
示例。 除非您以混淆所有含义的方式编写它,否则它就没有什么奇怪的 -
// replace IIFE with U
function Y(le) {
return (
function (f) { return f(f); }
U
(function (f) {
return le(function (x) {
return f(f)(x);
return U(f)(x)
});
})
)
}
// remove unnecessary outer (...)
function Y(le) {
return U(function (f) {
return le(function (x) {
return U(f)(x)
})
})
}
// simplified arrow expression
const Y = le =>
U (f => le (x => U (f) (x))
// alpha rename "f" to "recur"
const Y = le =>
U (recur => le (x => U (recur) (x))
// alpha rename "le" to "f"
const Y = f =>
U (recur => f (x => U (recur) (x))
与我们上面的Y
版本相匹配-
const Y = f =>
U (recur => f (x => U (recur) (x)))
没有你
我们可以扩展U
,使Y
具有独立的定义-
// starting with
const Y = f =>
U (recur => f (x => U (recur) (x)))
// expand inner U
const Y = f =>
U (recur => f (x => recur (recur) (x)))
// expand outer U
const Y = f =>
(recur => f (x => recur (recur) (x)))
(recur => f (x => recur (recur) (x)))
我们回到了IIFE,但是这次我们的定义更接近Haskell Curry的原始定义 -
const Y = f => (recur => f (x => recur (recur) (x))) (recur => f (x => recur (recur) (x))) const fact = Y (recur => n => n <= 2 ? n : n * recur (n - 1)) console.log (fact (5)) // 120
扩大我们的Y
直觉
像几乎所有其他Y
教程一样,Crockford演示了如何在具有单个参数的函数上使用Y
除了对Y
这些初步理解之外,还有很多潜在的潜伏性。 我将向您展示带有2个甚至3个参数的简单函数-
const Y = f => (recur => f (x => recur (recur) (x))) (recur => f (x => recur (recur) (x))) const range1 = Y (recur => m => n => m > n ? [] : [ m, ...recur (m + 1) (n) ]) const range2 = Y (recur => r => m => n => m > n ? r : recur ([ ...r, m ]) (m + 1) (n)) ([]) const fib = Y (recur => a => b => n => n === 0 ? a : recur (b) (a + b) (n - 1)) (0) (1) console.log (range1 (5) (10)) // [ 5, 6, 7, 8, 9, 10 ] console.log (range2 (5) (10)) // [ 5, 6, 7, 8, 9, 10 ] console.log (fib (10)) // 55
Y
可以表达的程序的复杂度实际上没有限制-
const Y = f =>
(recur => f (x => recur (recur) (x)))
(recur => f (x => recur (recur) (x)))
const reduce = Y (recur => i => f => state => a =>
i >= a.length
? state
: recur (i + 1) (f) (f (state, a[i])) (a)) (0)
const filter = f =>
reduce ((r, x) => f (x) ? [ ...r, x ] : r) ([])
const odds =
filter (n => n & 1)
console.log (odds ([ 0, 1, 2, 3, 4, 5, 6 ]))
// [ 1, 3, 5 ]
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.