Что такое y-combinator?

Y-combinator является концепцией comp-sci с "функциональной" стороны вещей. Большинство программистов вообще ничего не знают о них, если они даже слышали о них.

Что такое y-combinator? Как они работают? Для чего они хороши? Полезны ли они на процедурных языках?

Ответ 2

Y-combinator - это "функциональный" (функция, работающая на других функциях), которая позволяет рекурсию, когда вы не можете ссылаться на функцию изнутри. В теории компьютерной науки он обобщает рекурсию, абстрагируя ее реализацию и тем самым отделяя ее от фактической работы рассматриваемой функции. Преимущество не того, чтобы время компиляции для рекурсивной функции было своего рода бонусом. =)

Это применимо на языках, поддерживающих лямбда-функции. expression основанный на природе лямбда обычно означает, что они не могут ссылаться на себя по имени. И работая над этим, объявляя переменную, ссылаясь на нее, затем назначая ей лямбда, чтобы завершить цикл саморегуляции, является хрупким. Лямбда-переменная может быть скопирована, а исходная переменная переопределена, что нарушает самооценку.

Y-комбинаторы громоздки для реализации и часто использовать в статических типизированных языках (которые процедурные языки), потому что, как правило, ограничения на ввод текста требуют, чтобы количество аргументов для рассматриваемой функции было известно во время компиляции. Это означает, что Y-combinator должен быть записан для любого аргумента, который нужно использовать.

Ниже приведен пример использования и работы Y-Combinator в С#.

Использование Y-combinator предполагает "необычный" способ построения рекурсивной функции. Сначала вы должны написать свою функцию как часть кода, которая вызывает уже существующую функцию, а не сама:

// Factorial, if func does the same thing as this bit of code...
x == 0 ? 1: x * func(x - 1);

Затем вы превращаете это в функцию, которая принимает функцию для вызова, и возвращает функцию, которая делает это. Это называется функциональным, поскольку он принимает одну функцию и выполняет с ней операцию, что приводит к другой функции.

// A function that creates a factorial, but only if you pass in
// a function that does what the inner function is doing.
Func<Func<Double, Double>, Func<Double, Double>> fact =
  (recurs) =>
    (x) =>
      x == 0 ? 1 : x * recurs(x - 1);

Теперь у вас есть функция, которая принимает функцию и возвращает другую функцию, которая выглядит как факториал, но вместо того, чтобы называть себя, она вызывает аргумент, переданный во внешнюю функцию. Как вы делаете это факториалом? Передайте внутреннюю функцию себе. Y-combinator делает это, будучи функцией с постоянным именем, которое может ввести рекурсию.

// One-argument Y-Combinator.
public static Func<T, TResult> Y<T, TResult>(Func<Func<T, TResult>, Func<T, TResult>> F)
{
  return
    t =>  // A function that...
      F(  // Calls the factorial creator, passing in...
        Y(F)  // The result of this same Y-combinator function call...
              // (Here is where the recursion is introduced.)
        )
      (t); // And passes the argument into the work function.
}

Вместо факториального вызова, что происходит, факториал вызывает факториальный генератор (возвращаемый рекурсивным вызовом Y-Combinator). И в зависимости от текущего значения t функция, возвращаемая генератором, либо вызовет генератор снова, с t-1, либо просто вернет 1, завершая рекурсию.

Это сложный и загадочный, но все это дрожит во время выполнения, а ключом к его работе является "отсроченное исполнение" и разложение рекурсии на две функции. Внутренний F передан как аргумент, который будет вызываться в следующей итерации, только при необходимости.

Ответ 3

Я снял это с http://www.mail-archive.com/[email protected]/msg02716.html, который я объяснил несколько лет назад.

В этом примере я буду использовать JavaScript, но многие другие языки также будут работать.

Наша цель - записать рекурсивную функцию 1 переменная, использующая только функции из 1 переменных, и нет заданий, определения вещей по имени и т.д. (Почему это наш цель - еще один вопрос, позвольте просто принять это как вызов, который нам дают.) Кажется невозможным, да? В виде пример, разрешите реализовать факториал.

Ну, шаг 1 - сказать, что мы могли бы сделать это легко, если бы мы немного обманул. Используя функции двух переменных и мы можем по крайней мере избежать использования назначение для настройки рекурсии.

// Here the function that we want to recurse.
X = function (recurse, n) {
  if (0 == n)
    return 1;
  else
    return n * recurse(recurse, n - 1);
};

// This will get X to recurse.
Y = function (builder, n) {
  return builder(builder, n);
};

// Here it is in action.
Y(
  X,
  5
);

Теперь посмотрим, можем ли мы обмануть меньше. Хорошо, во-первых, мы используем но нам это не нужно. Мы можем просто написать X и Y inline.

// No assignment this time.
function (builder, n) {
  return builder(builder, n);
}(
  function (recurse, n) {
    if (0 == n)
      return 1;
    else
      return n * recurse(recurse, n - 1);
  },
  5
);

Но мы используем функции из 2 переменных, чтобы получить функцию 1 переменная. Можем ли мы это исправить? Ну умный парень по имени У Haskell Curry есть опрятный трюк, если у вас есть хороший более высокий порядок функции, то вам нужны только функции из 1 переменной. доказательство состоит в том, что вы можете получить от функций 2 (или более в общий случай) переменные до 1 переменной с чисто механическое преобразование текста следующим образом:

// Original
F = function (i, j) {
  ...
};
F(i,j);

// Transformed
F = function (i) { return function (j) {
  ...
}};
F(i)(j);

где... остается точно таким же. (Этот трюк называется "каррирование" после его изобретателя. Язык Haskell также названный в честь Хаскелла Карри. Файл, который при бесполезных мелочах.) Теперь просто примените это преобразование всюду, и получим наша окончательная версия.

// The dreaded Y-combinator in action!
function (builder) { return function (n) {
  return builder(builder)(n);
}}(
  function (recurse) { return function (n) {
    if (0 == n)
      return 1;
    else
      return n * recurse(recurse)(n - 1);
  }})(
  5
);

Не стесняйтесь попробовать. alert(), который возвращает, привязывает его к кнопке, что угодно. Этот код вычисляет факториалы, рекурсивно, без использования назначение, декларации или функции из 2 переменных. (Но пытаясь проследить, как это работает, вероятно, заставит голову вращаться. И передавая его, без вывода, слегка переформатированный приведет к появлению кода, который, несомненно, будет искажать и путать.)

Вы можете заменить 4 строки, которые рекурсивно определяют факториал с помощью любую другую рекурсивную функцию, которую вы хотите.

Ответ 4

Интересно, есть ли в попытке построить это с нуля. Посмотрим. Здесь основная, рекурсивная факториальная функция:

function factorial(n) {
    return n == 0 ? 1 : n * factorial(n - 1);
}

Предоставьте рефактору и создайте новую функцию под названием fact, которая возвращает анонимную факториальную вычислительную функцию вместо выполнения самого вычисления:

function fact() {
    return function(n) {
        return n == 0 ? 1 : n * fact()(n - 1);
    };
}

var factorial = fact();

Это немного странно, но в этом нет ничего плохого. Мы просто генерируем новую факториальную функцию на каждом шаге.

Рекурсия на этом этапе все еще довольно ясна. Функция fact должна знать свое имя. Пусть параметризует рекурсивный вызов:

function fact(recurse) {
    return function(n) {
        return n == 0 ? 1 : n * recurse(n - 1);
    };
}

function recurser(x) {
    return fact(recurser)(x);
}

var factorial = fact(recurser);

Это здорово, но recurser все еще нужно знать собственное имя. Пусть тоже параметризует:

function recurser(f) {
    return fact(function(x) {
        return f(f)(x);
    });
}

var factorial = recurser(recurser);

Теперь, вместо прямого вызова recurser(recurser), создайте функцию-обертку, которая возвращает ее результат:

function Y() {
    return (function(f) {
        return f(f);
    })(recurser);
}

var factorial = Y();

Теперь мы можем избавиться от имени recurser; это просто аргумент для внутренней функции Y, которая может быть заменена самой функцией:

function Y() {
    return (function(f) {
        return f(f);
    })(function(f) {
        return fact(function(x) {
            return f(f)(x);
        });
    });
}

var factorial = Y();

Единственное внешнее имя, на которое все еще ссылаются, - fact, но теперь должно быть ясно, что это тоже легко параметризует создание полного, общего решения:

function Y(le) {
    return (function(f) {
        return f(f);
    })(function(f) {
        return le(function(x) {
            return f(f)(x);
        });
    });
}

var factorial = Y(function(recurse) {
    return function(n) {
        return n == 0 ? 1 : n * recurse(n - 1);
    };
});

Ответ 5

В большинстве приведенных выше ответов описывается, что такое Y-combinator, но не то, для чего оно предназначено.

Компиляторы с фиксированными точками используются, чтобы показать, что лямбда-исчисление завершение завершено. Это очень важный результат в теории вычислений и обеспечивает теоретическую основу функционального программирования .

Изучение комбинаторов с фиксированной точкой также помогло мне понять функциональное программирование. Тем не менее, я никогда не использовал их для реального программирования.

Ответ 6

y-combinator в JavaScript:

var Y = function(f) {
  return (function(g) {
    return g(g);
  })(function(h) {
    return function() {
      return f(h(h)).apply(null, arguments);
    };
  });
};

var factorial = Y(function(recurse) {
  return function(x) {
    return x == 0 ? 1 : x * recurse(x-1);
  };
});

factorial(5)  // -> 120

Edit: Я многому учусь, глядя на код, но это немного сложно усвоить без какой-либо подготовки - извините. С некоторыми общими знаниями, представленными другими ответами, вы можете начать разбирать то, что происходит.

Функция Y - это "y-combinator". Теперь взгляните на строку var factorial, где используется Y. Обратите внимание, что вы передаете ему функцию, у которой есть параметр (в этом примере recurse), который также используется позже во внутренней функции. Имя параметра в основном становится именем внутренней функции, позволяющей ему выполнять рекурсивный вызов (поскольку в нем используется recurse()). y-combinator выполняет магию ассоциирования анонимной внутренней функции с именем параметра функция передана в Y.

Для полного объяснения того, как Y делает магию, проверил связанную статью (а не от меня кстати.)

Ответ 7

Для программистов, которые не сталкивались с функциональным программированием в глубине и не хотят начинать сейчас, но слегка любопытны:

Y combinator - это формула, которая позволяет реализовать рекурсию в ситуации, когда функции не могут иметь имена, но могут передаваться как аргументы, используемые как возвращаемые значения и определенные в других функциях.

Он работает, передавая функцию себе как аргумент, поэтому он может называть себя.

Это часть лямбда-исчисления, которая на самом деле является математикой, но фактически является языком программирования и довольно фундаментальна для информатики и особенно для функционального программирования.

Ежедневное практическое значение Y combinator ограничено, поскольку языки программирования, как правило, позволяют вам называть функции.

Если вам нужно идентифицировать его в полицейском составе, это выглядит так:

Y = λf. (λx.f(x x)) (λx.f(x x))

Обычно вы можете обнаружить это из-за повторения (λx.f (x x)).

Символы λ представляют собой гребенчатую букву лямбда, которая придает лямбда-исчислению свое имя, и там много терминов стиля (λx.t), потому что это выглядит как лямбда-исчисление.

Ответ 8

Другие ответы дают довольно краткий ответ на этот вопрос, без какого-либо важного факта: вам не нужно реализовывать комбинатор с фиксированными запятыми на любом практическом языке этим запутанным способом, и это не служит практическим целям (кроме "смотрите, я знаю, что Y-combinator is" ). Это важная теоретическая концепция, но мало практической ценности.

Ответ 9

Y-combinator - другое название конденсатора потока.

Ответ 10

Я написал своего рода "руководство идиотов" к Y-Combinator как в Clojure, так и в Scheme, чтобы помочь мне справиться с этим. Они находятся под влиянием материала в "Маленьком Schemer"

В схеме: https://gist.github.com/z5h/238891

или Clojure: https://gist.github.com/z5h/5102747

Оба учебника представляют собой код, чередующийся с комментариями, который должен быть разрезан и доступен в вашем любимом редакторе.

Ответ 11

Вот реализация JavaScript Y-Combinator и функции Factorial (из статьи Дугласа Крокфорда, доступной по адресу: http://javascript.crockford.com/little.html).

function Y(le) {
    return (function (f) {
        return f(f);
    }(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);

Ответ 12

y-combinator реализует анонимную рекурсию. Поэтому вместо

function fib( n ){ if( n<=1 ) return n; else return fib(n-1)+fib(n-2) }

вы можете сделать

function ( fib, n ){ if( n<=1 ) return n; else return fib(n-1)+fib(n-2) }

конечно, y-combinator работает только в языках со звонками. Если вы хотите использовать это в любом нормальном языке по умолчанию, тогда вам понадобится связанный z-combinator (y-combinator будет отклоняться/бесконечно-цикл).

Ответ 13

Комбинатор с фиксированной точкой (или оператор фиксированной точки) представляет собой функцию более высокого порядка, которая вычисляет неподвижную точку других функций. Эта операция имеет отношение к теории языка программирования, поскольку она позволяет реализовать рекурсию в виде правила перезаписи без явной поддержки от механизма выполнения языка. (src Wikipedia)

Ответ 14

Этот оператор может упростить вашу жизнь:

var Y = function(f) {
    return (function(g) {
        return g(g);
    })(function(h) {
        return function() {
            return f.apply(h(h), arguments);
        };
    });
};

Затем вы избегаете дополнительной функции:

var fac = Y(function(n) {
    return n == 0 ? 1 : n * this(n - 1);
});

Наконец, вы вызываете fac(5).

Ответ 15

Как новичок в комбинаторах, я нашел статью Майка Ванье (спасибо Николаю Манкусо), чтобы быть действительно полезной. Я хотел бы написать резюме, помимо документирования моего понимания, если бы это могло помочь некоторым другим, я был бы очень рад.

От Crappy до менее Crappy

Используя факториал в качестве примера, мы используем следующую функцию almost-factorial для вычисления факториала числа x:

def almost-factorial f x = if iszero x
                           then 1
                           else * x (f (- x 1))

В вышеприведенном псевдокоде almost-factorial принимает функцию f, а число x (almost-factorial находится в курсе, поэтому его можно рассматривать как взятие в функции f и возврат функции 1-arity).

Когда almost-factorial вычисляет факториал для x, он делегирует вычисление factorial для x - 1 для функции f и накапливает этот результат с помощью x (в этом случае он умножает результат (x - 1) с x).

Можно видеть, что almost-factorial принимает дерьмовую версию факториальной функции (которая может вычисляться только до номера x - 1) и возвращает менее дерьмовую версию факториала (который вычисляет до номера x). Как в этой форме:

almost-factorial crappy-f = less-crappy-f

Если мы повторно передадим менее трепетную версию factorial в almost-factorial, мы в итоге получим желаемую факториальную функцию f. Где это можно считать:

almost-factorial f = f

Фикс-точка

Тот факт, что almost-factorial f = f означает f, является фиксированной точкой функции almost-factorial.

Это был действительно интересный способ увидеть отношения вышеперечисленных функций, и для меня это был момент аха. (пожалуйста, прочитайте сообщение Майка по исправлению, если вы этого не сделали)

Три функции

Чтобы обобщить, мы имеем нерекурсивную функцию fn (например, нашу почти факториалную), у нас есть ее функция fix-point fr (например, наш f), то что Y делает, когда вы даете Y fn, Y возвращает функцию фиксированной точки fn.

Итак, в резюме (упрощенное, предполагая, что fr принимает только один параметр, x вырождается до x - 1, x - 2... в рекурсии):

  • Мы определяем основные вычисления как fn: def fn fr x = ...accumulate x with result from (fr (- x 1)), это почти полезная функция, хотя мы не можем использовать fn непосредственно на x, это будет полезно очень скоро. Эта нерекурсивная fn использует функцию fr для вычисления ее результата
  • fn fr = fr, fr является фиксированной точкой fn, fr является полезным funciton, мы можем использовать fr в x, чтобы получить наш результат
  • Y fn = fr, Y возвращает фиксированную точку функции, Y превращает нашу почти полезную функцию fn в полезную fr

Вывод Y (не входит в комплект)

Я пропущу вывод Y и пойду к пониманию Y. Сообщение Майка Вайнера содержит много деталей.

Форма Y

Y определяется как (в формате лямбда-исчисления):

Y f = λs.(f (s s)) λs.(f (s s))

Если мы заменим переменную s в левой части функций, получим

Y f = λs.(f (s s)) λs.(f (s s))
=> f (λs.(f (s s)) λs.(f (s s)))
=> f (Y f)

Действительно, результат (Y f) является фиксированной точкой f.

Почему работает (Y f)?

В зависимости от сигнатуры f, (Y f) может быть функцией любой arity, чтобы упростить, допустим, что (Y f) принимает только один параметр, например нашу факториальную функцию.

def fn fr x = accumulate x (fr (- x 1))

так как fn fr = fr, мы продолжаем

=> accumulate x (fn fr (- x 1))
=> accumulate x (accumulate (- x 1) (fr (- x 2)))
=> accumulate x (accumulate (- x 1) (accumulate (- x 2) ... (fn fr 1)))

рекурсивный расчет заканчивается, когда основной (fn fr 1) является базовым, а fn не использует fr в расчете.

Еще раз посмотрев Y:

fr = Y fn = λs.(fn (s s)) λs.(fn (s s))
=> fn (λs.(fn (s s)) λs.(fn (s s)))

Итак,

fr x = Y fn x = fn (λs.(fn (s s)) λs.(fn (s s))) x

Для меня магические части этой установки:

  • fn и fr взаимозависимы друг от друга: fr 'wraps' fn внутри, каждый раз, когда fr используется для вычисления x, он "порождает" ( "поднимает"?) fn и делегирует вычисление на fn (проходящий сам по себе fr и x); с другой стороны, fn зависит от fr и использует fr для вычисления результата меньшей задачи x-1.
  • В то время, когда fr используется для определения fn (когда fn использует fr в своих операциях), реальный fr еще не определен.
  • Он fn, который определяет реальную бизнес-логику. На основе fn, Y создает fr - вспомогательную функцию в определенной форме - для облегчения вычисления для fn рекурсивным образом.

Это помогло мне понять Y таким образом в данный момент, надеюсь, что это поможет.

Кстати, я также нашел книгу Введение в функциональное программирование через исчисление лямбдаочень хорошо, я только часть этого, и тот факт, что я не мог опустить голову в Y, привел меня к этому сообщению.

Ответ 16

Я думаю, что лучший способ ответить на этот вопрос - выбрать язык, например JavaScript:

function factorial(num)
{
    // If the number is less than 0, reject it.
    if (num < 0) {
        return -1;
    }
    // If the number is 0, its factorial is 1.
    else if (num == 0) {
        return 1;
    }
    // Otherwise, call this recursive procedure again.
    else {
        return (num * factorial(num - 1));
    }
}

Теперь перепишите его так, чтобы он не использовал имя функции внутри функции, но все равно вызывает ее рекурсивно.

Единственное место, которое должно видеть имя функции factorial, находится на сайте вызова.

Подсказка: вы не можете использовать имена функций, но вы можете использовать имена параметров.

Работаем с проблемой. Не смотрите. После того, как вы его решите, вы поймете, что проблема решает y-combinator.

Ответ 17

Факториал fact числа n может быть доказан

fact 0 = 1
fact n = n * fact (n - 1)

A комбинатор с фиксированной запятой представляет собой функцию более высокого порядка fix, которая, по определению, удовлетворяет эквивалентности

fix f = f (fix f)

fix f представляет решение x для уравнения с фиксированной точкой x = f x. Используя fix, произвольные конструктивные доказательства над общими/μ-рекурсивными функциями могут быть получены без несущественной самореференции.

fact n = (fix fact') n

где

fact' rec n = if n == 0 then 1 else n * rec (n - 1)

такое, что

   fact 3
=  (fix fact') 3
=  fact' (fix fact') 3
=  if 3 == 0 then 1 else 3 * (fix fact') (3 - 1)
=  3 * (fix fact') 2
=  3 * fact' (fix fact') 2
=  3 * if 2 == 0 then 1 else 2 * (fix fact') (2 - 1)
=  3 * 2 * (fix fact') 1
=  3 * 2 * fact' (fix fact') 1
=  3 * 2 * if 1 == 0 then 1 else 1 * (fix fact') (1 - 1)
=  3 * 2 * 1 * (fix fact') 0
=  3 * 2 * 1 * fact' (fix fact') 0
=  3 * 2 * 1 * if 0 == 0 then 1 else 0 * (fix fact') (0 - 1)
=  3 * 2 * 1 * 1
=  6

Это формальное доказательство того, что fact 3 = 6 методично использует эквивалент комбинаторной точки для переписывания fix fact' => fact' (fix fact'). Такой процесс называется анонимной рекурсией.


Формализм нетипизированного лямбда-исчисления состоит из контекстно-свободной грамматики

E ::= v        Variable
   |  λ v. E   Abstraction
   |  E E      Application

где v пробегает переменные вместе с правилами β- и eta-сокращения

(λ x. B) E  =>  B[x := E]   every free occurrence of x in B is substituted by E
  λ x. E x  =>  E           if x doesn't occur free in E

a свободен в λ b. b a, но не в λ a. λ b. b a. Эта-редукция часто опускается. Выражение, к которому применяется ни одно из двух правил сокращения, не является нормальным, либо называется канонической формой. λ x y. E является сокращением для λ x. λ y. E (многообразие), E F G является сокращением для (E F) G (левая ассоциативность). λ f x. f x и λ g y. g y являются альфа-эквивалентными.

Данные, такие как числа, кортежи, булевы, списки или деревья, не являются примитивными для нетипизированного лямбда-исчисления. Числа Церкви - это кодирование натуральных чисел, аналогичных перуанским аксиоматическим натуралам. Некоторые имена из этой кодировки:

0  =  λ f x. x                    No application
1  =  λ f x. f x                  Single application
2  =  λ f x. f (f x)              Twofold
3  =  λ f x. f (f (f x))          Threefold
 . . .

SUCC  =  λ n f x. f (n f x)       Successor
 ADD  =  λ n m f x. n f (m f x)   Addition
MULT  =  λ n m f x. n (m f) x     Multiplication

Формальное доказательство того, что 1 + 2 = 3, используя одно правило перезаписи бета-редукции:

   ADD                      1            2
=  (λ n m f x. n f (m f x)) (λ g y. g y) (λ h z. h (h z))
=  (λ m f x. (λ g y. g y) f (m f x)) (λ h z. h (h z))
=  (λ m f x. f (m f x)) (λ h z. h (h z))
=  λ f x. f ((λ h z. h (h z)) f x)
=  λ f x. f (f (f x))                                       Normal form
=  3

В лямбда-исчислении комбинаторы представляют собой абстракции, которые не содержат свободных переменных. Наиболее просто: I, комбинатор символов λ x. x. Такие комбинаторы являются примитивными операторами комбинаторных исчислений, как система SKI.

S  =  λ x y z. x z (y z)
K  =  λ x y. x
I  =  λ x. x

Бета-редукция не сильно нормализуется; не все приводимые выражения ( "redexes" ) сходятся к нормальной форме при бета-редукции. Простым примером является расходящее применение комбинатора омега-ω λ x. x x к себе.

   (λ x. x x) (λ y. y y)
=  (λ y. y y) (λ y. y y)   Substitution for variable x
. . .
=  ⊥                       Divergence/"Bottom"

В нормальном порядке приоритеты сокращаются приложением - самые левые подвыражения ( "головки" ). Аппликативный порядок нормализует аргументы перед подстановкой. Эти две стратегии аналогичны ленивым (например, Миранда, Хаскелл) и ожидающей (например, C, Scheme) оценкой соответственно.

(λ a b. a) ((λ i. i) n) ((λ x. x x) (λ y. y y))

не сходится к нормальной форме при бета-редукции аппликативного порядка:

   (λ a b. a) ((λ i. i) n) ((λ x. x x) (λ y. y y))
=  (λ a b. a) n ((λ x. x x) (λ y. y y))              Substitution for variable i
=  (λ a b. a) n ((λ y. y y) (λ y. y y))              For x
. . .
=  ⊥                                                 Bottom

Но выполняется при бета-редукции нормального порядка:

   (λ a b. a) ((λ i. i) n) ((λ x. x x) (λ y. y y))
=  (λ b. ((λ i. i) n)) ((λ x. x x) (λ y. y y))       Substitution for a
=  (λ i. i) n                                        For b
=  n                                                 For i; normal form

Если выражение имеет нормальную форму, бета-редукция нормального порядка найдет его.


Компонент Y с фиксированной запятой λ f. (λ x. f (x x)) (λ x. f (x x)) существенное свойство:

   Y g
=  (λ f. (λ x. f (x x)) (λ x. f (x x))) g
=  (λ x. g (x x)) (λ x. g (x x))           =  Y g
=  g ((λ x. g (x x)) (λ x. g (x x)))       =  g (Y g)
=  g (g ((λ x. g (x x)) (λ x. g (x x))))   =  g (g (Y g))
. . .                                      . . .

Y g = g (Y g) изоморфен fix f = f (fix f). Нетипированное лямбда-исчисление может кодировать произвольные конструктивные доказательства над общими/μ-рекурсивными функциями.

 FACT  =  λ n. Y FACT' n
FACT'  =  λ rec n. if n == 0 then 1 else n * rec (n - 1)

   FACT 3
=  (λ n. Y FACT' n) 3
=  Y FACT' 3
=  FACT' (Y FACT') 3                                         FACT' for f
=  if 3 == 0 then 1 else 3 * (Y FACT') (3 - 1)               Y FACT' for rec, 3 for n
=  3 * (Y FACT') (3 - 1)                                     Boolean decision
=  3 * FACT' (Y FACT') 2                                     FACT' for f
=  3 * if 2 == 0 then 1 else 2 * (Y FACT') (2 - 1)           . . .
=  3 * 2 * (Y FACT') 1
=  3 * 2 * FACT' (Y FACT') 1
=  3 * 2 * if 1 == 0 then 1 else 1 * (Y FACT') (1 - 1)
=  3 * 2 * 1 * (Y FACT') 0
=  3 * 2 * 1 * FACT' (Y FACT') 0
=  3 * 2 * 1 * if 0 == 0 then 1 else 0 * (Y FACT') (0 - 1)
=  3 * 2 * 1 * 1                                             Delayed multiplication
=  6                                                         Normal form

Бета-редукция нормального порядка делает нетипизированное лямбда-исчисление полной и эффективной системой перезаписи.


Для церковного нетипизированного лямбда-исчисления, как было показано, существует рекурсивно перечислимая бесконечность комбинаторов с фиксированной запятой, кроме Y.

 X  =  λ f. (λ x. x x) (λ x. f (x x))
Y'  =  (λ x y. x y x) (λ y x. y (x y x))
 Z  =  λ f. (λ x. f (λ v. x x v)) (λ x. f (λ v. x x v))
 Θ  =  (λ x y. y (x x y)) (λ x y. y (x x y))
  . . .

В Haskell fix можно элегантно реализовать как

fix :: forall a. (a -> a) -> a
fix f = f (fix f)

или

fix f = let { x = f x; } in x

Haskell laziness получает результат перед оценкой всех подвыражений.

primes :: Integral a => [a]
primes = sieve [2 ..]
   where
      sieve  =  fix (\ rec (p : ns) ->
                       p : rec [n | n <- ns
                                  , n `rem` p /= 0])