我正在摆弄JavaScript中的Cominators,当我偶然发现维基百科说:“Y组合器可以在SKI演算中表达为:Y = S(K(SII) ))(S(S(KS)K)(K(SII)))“,所以我不得不尝试:
var I = function (x) {
return x;
};
var K = function (x) {
return function(){
return x;}
};
var S = function (x) {
return function (y) {
return function (z) {
return x(z)(y(z));
}
}
};
var Y = S (K(S(I)(I))) (S(S(K(S))(K)) (K(S(I)(I))));
Y; //evals to:
//function (z) {return x(z)(y(z));}
//And this (lifted from Crockford's Site):
var factorial = Y(function (fac) {
return function (n) {
return n <= 2 ? n : n * fac(n - 1);
};
}); //fails:
//RangeError: Maximum call stack size exceeded
我做错了什么?我没有正确翻译那个表达吗?我怎么会这样做有什么问题吗?它甚至有意义吗?关于这样的事情的大部分内容只会让我的大脑想要爆炸,所以这个练习对我来说主要是为了看看我是否理解了这个符号(因此能够将它翻译成JavaScript)。
哦,顺便说一下:是什么让我阅读&amp;再次摆弄是prototype.js实现为Prototype.K实际上是I组合器。有人注意到了吗?
答案 0 :(得分:6)
这里的问题是您使用的是严格评估的编程语言。 Y-combinator与任何其他定点组合器非常相似,只有在需要调用函数或“懒惰评估”时才能正常工作。
我知道一种解决此问题的方法(one of my professors looked into it a while ago),但它会使您的代码完全无法读取。
下面我已经展示了究竟发生了什么,希望你能看到为什么JavaScript无法处理SKI-calculus的直接实现。
这是JavaScript评估SKI表达式后Y
的样子:
var Y = function (q) {
return (function(p){return q(p(p))})(function(p){return q(p(p))});
};
现在让我们看看如果你将它的函数function (fac) { ... }
提供给它会发生什么。我们称之为函数f
:
var factorial = (function(p){return f(p(p))})(function(p){return f(p(p))});
由于第一个匿名函数应用于参数,因此将对其进行求值:
var factorial = f(
(function(p){return f(p(p))})(function(p){return f(p(p))})
);
在一种延迟评估的语言中,f
的参数现在将保持不变,并且f
本身将被评估。但是,因为JavaScript是一种严格评估的语言(或“按值调用”),它想知道在实际运行该函数之前需要将哪个参数传递给函数f
。那么,让我们评估一下这个论点,不管吗?
var factorial = f(f(
(function(p){return f(p(p))})(function(p){return f(p(p))})
)
);
我想现在你开始看到现在出了问题的地方,以及Y-combinator实际上是如何工作的。在任何情况下,您的JavaScript机器都将耗尽堆栈空间,因为它正在尝试构建对f
的无限堆栈调用。