Y-комбинатор - это концепция информатики с «функциональной» стороны вещей. Большинство программистов вообще ничего не знают о комбинаторах, даже слышали ли они о них.
- Что такое Y-комбинатор?
- Как работают комбинаторы?
- Для чего они хороши?
- Полезны ли они на процедурных языках?
functional-programming
computer-science
theory
definition
combinators
Крис Аммерман
источник
источник
Ответы:
Если вы готовы к длительному чтению, у Майка Ванье есть отличное объяснение . Короче говоря, он позволяет вам реализовать рекурсию на языке, который не обязательно поддерживает ее изначально.
источник
Y-комбинатор - это «функционал» (функция, которая работает с другими функциями), который включает рекурсию, когда вы не можете обратиться к функции изнутри. В теории информатики это обобщает рекурсию , абстрагирует ее реализацию и тем самым отделяет ее от фактической работы рассматриваемой функции. Преимущество отсутствия необходимости в имени компиляции для рекурсивной функции является своего рода бонусом. знак равно
Это применимо к языкам, которые поддерживают лямбда-функции . Выражение основанная природа лямбд обычно означает , что они не могут называть себя по имени. И обходить это путем объявления переменной, обращения к ней, а затем присвоения ей лямбды, чтобы завершить цикл самоссылки, является хрупким. Лямбда-переменная может быть скопирована, а исходная переменная переназначена, что нарушает самоссылку.
Y-комбинаторы громоздки для реализации и часто используются в статически-типизированных языках (которыми часто являются процедурные языки ), потому что обычно ограничения типа требуют, чтобы число аргументов для рассматриваемой функции было известно во время компиляции. Это означает, что y-комбинатор должен быть записан для любого количества аргументов, которое нужно использовать.
Ниже приведен пример использования и работы Y-Combinator в C #.
Использование Y-комбинатора предполагает «необычный» способ построения рекурсивной функции. Сначала вы должны написать свою функцию в виде фрагмента кода, который вызывает уже существующую функцию, а не себя:
Затем вы превращаете это в функцию, которая принимает функцию для вызова и возвращает функцию, которая делает это. Это называется функционалом, потому что он берет одну функцию и выполняет с ней операцию, которая приводит к другой функции.
Теперь у вас есть функция, которая принимает функцию и возвращает другую функцию, которая выглядит как факториал, но вместо вызова самой себя она вызывает аргумент, переданный во внешнюю функцию. Как вы делаете это факториалом? Передайте внутреннюю функцию себе. Y-Combinator делает это, будучи функцией с постоянным именем, которая может вводить рекурсию.
Вместо того, чтобы вызывать сам факториал, происходит то, что факториал вызывает генератор факториала (возвращаемый рекурсивным вызовом Y-Combinator). И в зависимости от текущего значения t функция, возвращаемая из генератора, либо снова вызовет генератор, с t - 1, либо просто вернет 1, завершая рекурсию.
Это сложно и загадочно, но все встряхивается во время выполнения, и ключом к его работе является «отложенное выполнение» и разбиение рекурсии на две функции. Внутренний F передается в качестве аргумента для вызова на следующей итерации, только если это необходимо .
источник
fix :: (a -> a) -> a
и,a
в свою очередь, может быть функцией столько аргументов, сколько вам нужно. Это означает, что статическая типизация на самом деле не делает это громоздким.Я поднял это с http://www.mail-archive.com/boston-pm@mail.pm.org/msg02716.html которое является объяснением, которое я написал несколько лет назад.
Я буду использовать JavaScript в этом примере, но многие другие языки также будут работать.
Наша цель состоит в том, чтобы иметь возможность написать рекурсивную функцию из 1 переменной, используя только функции из 1 переменной и без присваиваний, определяя вещи по имени и т. Д. (Почему это наша цель, это другой вопрос, давайте просто примем это как вызов, который мы .) Кажется невозможным, а? В качестве примера давайте реализуем факториал.
Ну, шаг 1 - сказать, что мы могли бы сделать это легко, если бы немного обманули. Используя функции 2 переменных и присваивания, мы можем по крайней мере избежать использования присваивания для настройки рекурсии.
Теперь посмотрим, сможем ли мы обмануть меньше. Ну, во-первых, мы используем назначение, но нам не нужно. Мы можем просто написать X и Y в строке.
Но мы используем функции от 2 переменных, чтобы получить функцию от 1 переменной. Мы можем это исправить? У умного парня по имени Haskell Curry есть хитрый трюк: если у вас есть хорошие функции более высокого порядка, вам нужны только функции с 1 переменной. Доказательством является то, что вы можете получить из функций 2 (или более в общем случае) переменных 1 переменную с помощью чисто механического преобразования текста, например:
где ... остается точно таким же. (Этот трюк назван «карри» по имени его изобретателя. Язык Haskell также назван по имени Haskell Curry. Файл, который находится под бесполезными мелочами.) Теперь просто примените это преобразование везде, и мы получим нашу окончательную версию.
Не стесняйтесь попробовать это. alert (), которые возвращают, привязывают к кнопке, что угодно. Этот код вычисляет факториалы рекурсивно, без использования присваивания, объявлений или функций двух переменных. (Но попытка проследить, как это работает, может привести к тому, что ваша голова закружится. А передача ее без деривации, только слегка переформатированная, приведет к коду, который наверняка сбьет с толку и запутает.)
Вы можете заменить 4 строки, которые рекурсивно определяют факториал, любой другой рекурсивной функцией, которую вы хотите.
источник
function (n) { return builder(builder)(n);}
вместоbuilder(builder)
?Интересно, есть ли смысл пытаться построить это с нуля. Посмотрим. Вот основная рекурсивная факториальная функция:
Давайте проведем рефакторинг и создадим новую функцию с именем,
fact
которая возвращает анонимную функцию вычисления факториала вместо выполнения самого вычисления:Это немного странно, но в этом нет ничего плохого. Мы просто генерируем новую факториальную функцию на каждом шаге.
На этом этапе рекурсия все еще довольно явная.
fact
Функция должна быть в курсе своего имени. Давайте параметризуем рекурсивный вызов:Это здорово, но
recurser
все равно нужно знать свое имя. Давайте также параметризуем это:Теперь вместо
recurser(recurser)
непосредственного вызова создадим функцию-оболочку, которая возвращает свой результат:Теперь мы можем полностью избавиться от
recurser
имени; это просто аргумент внутренней функции Y, которую можно заменить самой функцией:Единственное внешнее имя, на которое все еще ссылаются, -
fact
это уже ясно, но оно также легко параметризуется, создавая полное, общее решение:источник
recurser
. Ни малейшего понятия, что он делает или почему.recurser
функция - первый шаг к этой цели, потому что она дает нам рекурсивную версию,fact
которая никогда не ссылается на себя по имени.function Y(recurse) { return recurse(recurse); } let factorial = Y(creator => value => { return value == 0 ? 1 : value * creator(creator)(value - 1); });
. И вот как я переварить это (не уверен, если это правильно): не явно ссылаясь на функцию (не допускается как комбинатор ), мы можем использовать две частично примененные / карри функции (функция создателя и функция вычисления), с какие мы можем создавать лямбда / анонимные функции, которые достигают рекурсии без необходимости имени для функции вычисления?Большинство ответов выше описывают, что такое Y-комбинатор , но не то, для чего он .
Неподвижные точки комбинаторов используются , чтобы показать , что лямбда - исчисление является Тьюринг . Это очень важный результат в теории вычислений и обеспечивает теоретическую основу для функционального программирования .
Изучение комбинаторов с фиксированной запятой также помогло мне понять функциональное программирование. Я никогда не нашел их применения в реальном программировании.
источник
y-комбинатор в JavaScript :
редактировать : я многому научился, глядя на код, но этот немного сложно проглотить без некоторого фона - извините за это. С некоторыми общими знаниями, представленными другими ответами, вы можете начать понимать, что происходит.
Функция Y - это y-комбинатор. Теперь взгляните на
var factorial
строку, где используется Y. Обратите внимание, что вы передаете ему функцию, у которой есть параметр (в этом примереrecurse
), который также используется позже во внутренней функции. Имя параметра в основном становится именем внутренней функции, позволяющей ему выполнять рекурсивный вызов (так как он используетrecurse()
в своем определении.) Y-комбинатор выполняет магию ассоциирования анонимной внутренней функции с именем параметра функции, передаваемой в Y.Для полного объяснения того, как Y делает волшебство, проверил связанную статью (не мной между прочим.)
источник
arguments.callee
не допускается в строгом режиме: developer.mozilla.org/en/JavaScript/…(function fact(n){ return n <= 1? 1 : n * fact(n-1); })(5)
Для программистов, которые не сталкивались с функциональным программированием в глубине и не хотят начинать сейчас, но немного любопытны:
Комбинатор Y - это формула, которая позволяет реализовать рекурсию в ситуации, когда функции не могут иметь имен, но могут передаваться в качестве аргументов, использоваться в качестве возвращаемых значений и определяться в других функциях.
Он работает, передавая функцию себе в качестве аргумента, чтобы он мог вызывать сам себя.
Это часть лямбда-исчисления, которое на самом деле математическое, но фактически является языком программирования и довольно фундаментально для компьютерных наук и особенно для функционального программирования.
Повседневная практическая ценность Y-комбинатора ограничена, поскольку языки программирования, как правило, позволяют вам называть функции.
В случае, если вам нужно идентифицировать его в полицейском составе, это выглядит так:
Обычно вы можете заметить это из-за повторения
(λx.f (x x))
.Эти
λ
символы являются греческая буква лямбда, который дает лямбда - исчисление его имени, и есть много(λx.t)
терминов типа , потому что это то , что лямбда - исчисление выглядит.источник
U x = x x
,Y = U . (. U)
(злоупотребляя Haskell-подобной записи). IOW, с правильными комбинаторамиY = BU(CBU)
. Таким образом,Yf = U (f . U) = (f . U) (f . U) = f (U (f . U)) = f ((f . U) (f . U))
.Анонимная рекурсия
Комбинатор с фиксированной точкой - это функция высшего порядка,
fix
которая по определению удовлетворяет эквивалентностиfix f
представляет собой решение уравненияx
с фиксированной точкойФакториал натурального числа может быть доказан
Используя
fix
произвольные конструктивные доказательства над общими / µ-рекурсивными функциями, можно получить без одноименной самоссылки.где
такой, что
Это формальное доказательство того, что
методично использует эквивалентность комбинатора с фиксированной запятой для переписывания
Лямбда-исчисление
Нетипизированная лямбда - исчисление формализм состоит в контекстно-свободной грамматике
где
v
колеблется над переменными, вместе с правилами сокращения бета и этаБета-редукция заменяет все свободные вхождения переменной
x
в теле абстракции («функция»)B
выражением («аргумент»)E
. Eta сокращение устраняет избыточную абстракцию. Иногда это исключается из формализма. Неприводимое выражение, к которому не применяется ни одно правило сокращения, находится в нормальной или канонической форме .это сокращение для
(абстракция мультиарность),
это сокращение для
(приложение левого ассоциативности),
а также
являются альфа-эквивалентны .
Абстракция и приложение - это только два «языковых примитива» лямбда-исчисления, но они позволяют кодировать произвольно сложные данные и операции.
Церковные цифры представляют собой кодировку натуральных чисел, сходных с пеаноаксиоматическими натуралами.
Формальное доказательство того, что
используя правило перезаписи бета-сокращения:
Комбинаторы
В лямбда-исчислении комбинаторы - это абстракции, которые не содержат свободных переменных. Проще всего:
I
тождественный комбинаторизоморфна тождественной функции
Такие комбинаторы являются примитивными операторами исчислений комбинаторов, таких как система SKI.
Бета-снижение не сильно нормализуется ; не все приводимые выражения, «redexes», сходятся к нормальной форме при бета-редукции. Простой пример - расходящееся применение
ω
комбинатора омегак себе:
Сокращение крайних левых подвыражений («головы») является приоритетным. Аппликативный порядок нормализует аргументы перед заменой, нормальный порядок - нет. Эти две стратегии аналогичны активной оценке, например, C, и ленивой оценке, например, Haskell.
расходится под стремительное снижение бета аппликативного порядка
т.к. в строгой семантике
но сходится при ленивом уменьшении бета нормального порядка
Если выражение имеет нормальную форму, бета-редукция нормального порядка найдет его.
Y
Существенное свойство комбинатора с
Y
фиксированной точкойдан кем-то
Эквивалентность
изоморфен
Нетипизированное лямбда-исчисление может кодировать произвольные конструктивные доказательства над общими / µ-рекурсивными функциями.
(Умножение отложено, слияние)
Было показано, что для нетипизированного лямбда-исчисления Черча существует рекурсивно перечисляемая бесконечность комбинаторов с фиксированной запятой
Y
.Бета-редукция нормального порядка делает нерасширенное нетипизированное лямбда-исчисление системой полного переписывания по Тьюрингу.
В Haskell элегантно реализован комбинатор с фиксированной точкой
Лень Хаскелла нормализуется до конечности до того, как все подвыражения будут оценены.
источник
λ x . x
, как ты сегодня?Другие ответы дают довольно краткий ответ на этот вопрос, без одного важного факта: вам не нужно реализовывать комбинатор с фиксированной запятой на любом практическом языке таким сложным образом, и это не имеет никакого практического смысла (кроме «посмотрите, я знаю, что такое Y-комбинатор»). является"). Это важная теоретическая концепция, но мало практической ценности.
источник
Вот JavaScript-реализация Y-Combinator и функции Factorial (из статьи Дугласа Крокфорда, доступной по адресу: http://javascript.crockford.com/little.html ).
источник
Y-Combinator - другое название конденсатора потока.
источник
Я написал своего рода «руководство для идиотов» для Y-Combinator как в Clojure, так и в Схеме, чтобы помочь себе в этом разобраться. На них влияет материал в «Маленьком интриганке»
На схеме: https://gist.github.com/z5h/238891
или Clojure: https://gist.github.com/z5h/5102747
Оба руководства содержат код с комментариями и должны быть вставлены в ваш любимый редактор.
источник
Как новичок в комбинаторах, я нашел статью Майка Ванье (спасибо Николасу Манкузо) очень полезной. Я хотел бы написать резюме, помимо документирования моего понимания, если бы оно могло помочь некоторым другим, я был бы очень рад.
От дрянного к менее дрянному
Используя факториал в качестве примера, мы используем следующую
almost-factorial
функцию для вычисления факториала числаx
:В приведенном выше псевдокоде
almost-factorial
принимает функциюf
и числоx
(almost-factorial
каррируется, поэтому его можно рассматривать как функциюf
и возвращение функции 1-арности).Когда
almost-factorial
вычисляется факториал дляx
, он делегирует вычисление факториала дляx - 1
функцииf
и накапливает этот результат сx
(в этом случае он умножает результат (x - 1) на x).Это можно увидеть как
almost-factorial
берет в дрянной версии факториальной функции (которая может рассчитывать только до числаx - 1
) и возвращает менее дрянную версию факториала (которая рассчитывает до числаx
). Как в этой форме:Если мы несколько раз передадим менее дрянную версию факториала
almost-factorial
, мы в итоге получим желаемую факториальную функциюf
. Где это можно рассматривать как:Fix-точка
Тот факт, что
almost-factorial f = f
означает,f
является фиксированной точкой функцииalmost-factorial
.Это был действительно интересный способ увидеть взаимосвязь функций, описанных выше, и это был момент для меня. (пожалуйста, прочитайте сообщение Майка о точках, если вы этого не сделали)
Три функции
В общем, у нас есть нерекурсивная функция
fn
(например, наша почти факторная), у нас есть ее функция с фиксированной точкой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
определяется как (в формате лямбда-исчисления ):Если мы заменим переменную
s
слева от функций, мы получимТак что, действительно, результатом
(Y f)
является фиксированная точкаf
.Почему
(Y f)
работает?В зависимости от сигнатуры
f
,(Y f)
может быть функцией любой арности, для упрощения предположим, что принимает(Y f)
только один параметр, как наша факторная функция.с тех пор
fn fr = fr
мы продолжаемрекурсивное вычисление завершается, когда самый внутренний
(fn fr 1)
является базовым случаем иfn
не используетсяfr
в вычислении.Глядя на еще
Y
раз:Так
Для меня волшебными частями этой установки являются:
fn
иfr
взаимозависимы друг с другом:fr
«обертки»fn
внутри, каждый разfr
используется для вычисленияx
, он «порождает» («поднимает»?)fn
и делегирует вычисленияfn
(передавая сам по себеfr
иx
); с другой стороны,fn
зависит отfr
и используетfr
для вычисления результата меньшей задачиx-1
.fr
используется для определенияfn
(когдаfn
используетсяfr
в своих операциях), реальноеfr
еще не определено.fn
что определяет настоящую бизнес-логику. На основеfn
,Y
создаетfr
- вспомогательная функция в определенной форме - чтобы облегчить расчет дляfn
в рекурсивной форме.Это помогло мне понять
Y
это на данный момент, надеюсь, это поможет.Кстати, я также посчитал книгу «Введение в функциональное программирование с помощью лямбда-исчисления» очень хорошей, я только часть ее, и тот факт, что я не мог разобраться
Y
в книге, привел меня к этому посту.источник
Здесь приведены ответы на оригинальные вопросы , составленные из статьи (которую ВСЕГО стоит прочитать), упомянутой в ответе Николаса Манкузо , а также другие ответы:
Y-комбинатор - это «функционал» (или функция более высокого порядка - функция, которая работает с другими функциями), которая принимает один аргумент, который является нерекурсивной функцией, и возвращает версию функции, которая является рекурсивным.
Несколько рекурсивно =), но более глубокое определение:
Комбинатор - это просто лямбда-выражение без свободных переменных.
Свободная переменная - это переменная, которая не является связанной переменной.
Связанная переменная - переменная, которая содержится в теле лямбда-выражения, в котором в качестве одного из аргументов указано имя этой переменной.
Еще один способ думать об этом заключается в том, что комбинатор - это такое лямбда-выражение, в котором вы можете заменить имя комбинатора на его определение везде, где оно найдено, и все будет работать (вы попадете в бесконечный цикл, если комбинатор содержать ссылку на себя, внутри лямбда-тела).
Y-комбинатор - это комбинатор с фиксированной точкой.
Фиксированная точка функции - это элемент области функции, который сопоставлен с ней самой функцией.
То есть
c
является фиксированной точкой функции,f(x)
еслиf(c) = c
это означает
f(f(...f(c)...)) = fn(c) = c
Примеры ниже предполагают сильную + динамическую типизацию:
Ленивый (нормальный порядок) Y-комбинатор:
это определение применяется к языкам с отложенной (также отложенной, по требованию) оценкой - стратегией оценки, которая задерживает оценку выражения до тех пор, пока не потребуется его значение.
Это означает, что для данной функции
f
(которая является нерекурсивной функцией) соответствующую рекурсивную функцию можно сначала получить путем вычисленияλx.f(x x)
, а затем применить это лямбда-выражение к себе.Строгий (аппликативный порядок) Y-комбинатор:
это определение применяется к языкам со строгой (также: жадной, жадной) оценкой - стратегией оценки, в которой выражение оценивается, как только оно связано с переменной.
Он такой же ленивый по своей природе, у него просто есть дополнительные
λ
обертки, чтобы задержать оценку тела лямбды. Я задал еще один вопрос , несколько связанный с этой темой.Украденноезаимствовано из ответа Криса Аммермана : Y-комбинатор обобщает рекурсию, абстрагирует ее реализацию и тем самым отделяет ее от фактической работы рассматриваемой функции.Несмотря на то, что Y-комбинатор имеет некоторые практические применения, это в основном теоретическая концепция, понимание которой расширит ваше общее видение и, вероятно, увеличит ваши аналитические и опытные навыки.
Как заявил Майк Ванье : во многих статически типизированных языках можно определить Y-комбинатор, но (по крайней мере в примерах, которые я видел), такие определения обычно требуют некоторого неочевидного взлома типов, поскольку сам Y-комбинатор не ' не имеет простой статический тип. Это выходит за рамки этой статьи, поэтому я не буду упоминать это дальше
И, как отметил Крис Аммерман : большинство процедурных языков имеют статическую типизацию.
Так что ответ на этот вопрос - не совсем.
источник
Y-комбинатор реализует анонимную рекурсию. Так что вместо
ты можешь сделать
Конечно, Y-комбинатор работает только на языках по имени. Если вы хотите использовать это на любом обычном языке вызовов по значению, вам понадобится соответствующий z-комбинатор (у-комбинатор будет расходиться / бесконечный цикл).
источник
Комбинатор с фиксированной точкой (или оператор с фиксированной точкой) - это функция высшего порядка, которая вычисляет фиксированную точку других функций. Эта операция актуальна в теории языка программирования, потому что она позволяет реализовать рекурсию в форме правила перезаписи, без явной поддержки со стороны движка языка. (Википедия)
источник
Оператор this может упростить вашу жизнь:
Тогда вы избегаете дополнительной функции:
Наконец, вы звоните
fac(5)
.источник
Я думаю, что лучший способ ответить на этот вопрос - выбрать язык, такой как JavaScript:
Перепишите его так, чтобы оно не использовало имя функции внутри функции, но все равно вызывало его рекурсивно.
Единственное место название функции
factorial
должно быть видно - это место вызова.Подсказка: вы не можете использовать имена функций, но вы можете использовать имена параметров.
Работа проблема. Не ищи это. Решив ее, вы поймете, какую проблему решает y-комбинатор.
источник