Проблема в том, что вы используете строго оцененный язык программирования. Y-комбинатор, как и любой другой комбинатор с фиксированной точкой, будет работать правильно только тогда, когда ваши функции вызываются по необходимости или «лениво оцениваются».
Я знаю способ обойти это ( один из моих профессоров некоторое время назад изучил это ), но это сделает ваш код полностью нечитаемым.
Ниже я точно показал, что происходит, надеясь, что вы поймете, почему JavaScript не может выполнить простую реализацию SKI-исчисления.
Вот как Y
выглядит после того, как JavaScript оценил ваше SKI-выражение:
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-комбинатор. В любом случае, вашему компьютеру JavaScript не хватит места в стеке, потому что он пытается создать бесконечный стек вызовов для f
.