В комбинаторной логике для компьютерных наук комбинатор с фиксированной точкой (или комбинатор с фиксированной точкой ), [1] : стр. 26 — это функция высшего порядка (т. е. функция, которая принимает функцию в качестве аргумента ), которая возвращает некоторую фиксированную точку (значение, которое отображается само на себя) своей функции-аргумента, если таковая существует.
Формально, если — комбинатор с фиксированной точкой и функция имеет одну или несколько фиксированных точек, то — одна из этих фиксированных точек, т.е.
Комбинаторы с фиксированной точкой могут быть определены в лямбда-исчислении и в языках функционального программирования и предоставляют средства для рекурсивных определений .
В классическом нетипизированном лямбда-исчислении каждая функция имеет фиксированную точку. Конкретной реализацией является парадоксальный комбинатор Y Хаскеля Карри , заданный как [2] : 131 [примечание 1] [примечание 2]
(Здесь мы используем стандартные обозначения и соглашения лямбда-исчисления: Y — это функция, которая принимает один аргумент f и возвращает все выражение, следующее за первой точкой; выражение обозначает функцию, которая принимает один аргумент x , рассматриваемую как функция, и возвращает выражение , где обозначает x, примененное к себе. Сопоставление выражений обозначает применение функции, является левоассоциативным и имеет более высокий приоритет, чем точка.)
Следующий расчет подтверждает, что это действительно неподвижная точка функции :
Лямбда-член в общем случае не может β-редуктироваться к члену . Однако оба члена β-редуктируются к одному и тому же члену, как показано.
Примененный к функции с одной переменной, комбинатор Y обычно не завершается. Более интересные результаты получаются при применении комбинатора Y к функциям двух или более переменных. Дополнительные переменные могут использоваться как счетчик или индекс. Полученная функция ведет себя как цикл while или for в императивном языке.
При таком использовании комбинатор Y реализует простую рекурсию . Лямбда-исчисление не позволяет функции появляться в качестве термина в ее собственном определении, как это возможно во многих языках программирования, но функция может быть передана в качестве аргумента функции более высокого порядка, которая применяет ее рекурсивно.
Комбинатор Y также может использоваться для реализации парадокса Карри . Суть парадокса Карри в том, что нетипизированное лямбда-исчисление несостоятельно как дедуктивная система, и комбинатор Y демонстрирует это, позволяя анонимному выражению представлять ноль или даже множество значений. Это непоследовательно в математической логике.
Ниже представлен пример реализации комбинатора Y на двух языках.
# Y-комбинатор в PythonY = лямбда f : ( лямбда x : f ( x ( x )))( лямбда x : f ( x ( x )))Д ( Д )
// Y Combinator в C++, с использованием расширений C++ 14инт мейн () { авто Y = []( авто f ) { авто f1 = [ f ]( авто x ) -> decltype ( f ) { вернуть f ( x ( x )); }; вернуть f1 ( f1 ); }; Д ( Д );}
Обратите внимание, что обе эти программы, хотя формально и правильны, на практике бесполезны; обе они бесконечно зацикливаются, пока не завершатся из-за переполнения стека . В более общем смысле, поскольку и Python, и C++ используют строгую оценку , комбинатор Y в этих языках, как правило, бесполезен; см. ниже комбинатор Z, который можно использовать в строгих языках программирования .
Комбинатор Y — это реализация комбинатора с фиксированной точкой в лямбда-исчислении. Комбинаторы с фиксированной точкой также могут быть легко определены в других функциональных и императивных языках. Реализация в лямбда-исчислении сложнее из-за ограничений в лямбда-исчислении. Комбинатор с фиксированной точкой может использоваться в ряде различных областей:
Комбинаторы с фиксированной точкой могут применяться к ряду различных функций, но обычно не завершаются, если нет дополнительного параметра. Когда функция, которую нужно исправить, ссылается на свой параметр, вызывается другой вызов функции, поэтому вычисление никогда не начинается. Вместо этого дополнительный параметр используется для запуска начала вычисления.
Тип фиксированной точки — это возвращаемый тип фиксируемой функции. Это может быть вещественное число, функция или любой другой тип.
В нетипизированном лямбда-исчислении функция, к которой применяется комбинатор с фиксированной точкой, может быть выражена с использованием кодировки, например кодировки Чёрча . В этом случае конкретные лямбда-термы (определяющие функции) рассматриваются как значения. «Выполнение» (бета-редукция) комбинатора с фиксированной точкой в кодировке даёт лямбда-терм для результата, который затем может быть интерпретирован как значение с фиксированной точкой.
С другой стороны, функцию можно рассматривать как лямбда-терм, определенный исключительно в лямбда-исчислении.
Эти различные подходы влияют на то, как математик и программист могут рассматривать комбинатор с фиксированной точкой. Математик может рассматривать комбинатор Y, примененный к функции, как выражение, удовлетворяющее уравнению с фиксированной точкой, и, следовательно, как решение.
Напротив, человек, желающий применить комбинатор с фиксированной точкой только к какой-то общей задаче программирования, может рассматривать его только как средство реализации рекурсии.
Многие функции не имеют фиксированных точек, например , с . Используя кодировку Чёрча , натуральные числа могут быть представлены в лямбда-исчислении, и эта функция f может быть определена в лямбда-исчислении. Однако теперь ее область определения будет содержать все лямбда-выражения, а не только те, которые представляют натуральные числа. Комбинатор Y, примененный к f , даст фиксированную точку для f , но эта фиксированная точка не будет представлять натуральное число. Если попытаться вычислить Y f на реальном языке программирования, возникнет бесконечный цикл.
Комбинатор с фиксированной точкой может быть определен в математике, а затем реализован в других языках. Общая математика определяет функцию на основе ее экстенсиональных свойств. [3] То есть, две функции равны, если они выполняют одно и то же отображение. Лямбда-исчисление и языки программирования рассматривают идентичность функции как интенсиональное свойство. Идентичность функции основана на ее реализации.
Функция (или терм) лямбда-исчисления — это реализация математической функции. В лямбда-исчислении существует ряд комбинаторов (реализаций), которые удовлетворяют математическому определению комбинатора с фиксированной точкой.
Комбинаторная логика — это теория функций высшего порядка . Комбинатор — это закрытое лямбда-выражение, то есть оно не имеет свободных переменных . Комбинаторы можно объединять для направления значений на их правильные места в выражении, даже не называя их переменными.
Комбинаторы с фиксированной точкой могут использоваться для реализации рекурсивного определения функций. Однако они редко используются в практическом программировании. [4] Сильно нормализующие системы типов, такие как просто типизированное лямбда-исчисление, запрещают незавершение, и поэтому комбинаторам с фиксированной точкой часто нельзя назначить тип или требуются сложные функции системы типов. Кроме того, комбинаторы с фиксированной точкой часто неэффективны по сравнению с другими стратегиями реализации рекурсии, поскольку они требуют большего количества сокращений функций и создают и разбирают кортеж для каждой группы взаимно рекурсивных определений. [1] : страница 232
Функция факториала представляет собой хороший пример того, как комбинатор с фиксированной точкой может быть использован для определения рекурсивных функций. Стандартное рекурсивное определение функции факториала в математике можно записать как
где n — неотрицательное целое число. Если мы хотим реализовать это в лямбда-исчислении, где целые числа представлены с использованием кодировки Чёрча , мы сталкиваемся с проблемой, что лямбда-исчисление не позволяет использовать имя функции («факт») в определении функции. Это можно обойти с помощью комбинатора с фиксированной точкой следующим образом.
Определим функцию F двух аргументов f и n :
(Вот функция, которая принимает два аргумента и возвращает свой первый аргумент, если n = 0, и свой второй аргумент в противном случае; результатом ее вычисления является n -1.)
Теперь определим . Тогда — неподвижная точка F , что дает
по желанию.
Комбинатор Y, открытый Хаскеллом Б. Карри , определяется как
В нетипизированном лямбда-исчислении комбинаторы с фиксированной точкой не особенно редки. Фактически их бесконечно много. [5] В 2005 году Майер Голдберг показал, что множество комбинаторов с фиксированной точкой нетипизированного лямбда-исчисления рекурсивно перечислимо . [6]
Комбинатор Y можно выразить в исчислении SKI как
Дополнительные комбинаторы ( система B, C, K, W ) позволяют получить гораздо более короткое определение. С самоприменяемым комбинатором, поскольку и , вышесказанное становится
Простейший комбинатор с фиксированной точкой в SK-исчислении, найденный Джоном Тромпом , — это
хотя обратите внимание, что это не в нормальной форме, которая длиннее. Этот комбинатор соответствует лямбда-выражению
Следующий комбинатор с фиксированной точкой проще, чем комбинатор Y, и β-редуцируется в комбинатор Y; иногда его называют самим комбинатором Y:
Другим распространенным комбинатором с фиксированной точкой является комбинатор Тьюринга с фиксированной точкой (названный в честь его первооткрывателя Алана Тьюринга ): [7] [2] : 132
Его преимущество перед состоит в том, что бета-редуцирует к , [примечание 3] тогда как и только бета-редуцируют к общему члену.
также имеет простую форму вызова по значению:
Аналогом взаимной рекурсии является поливариантный комбинатор с фиксированной точкой [8] [9] [10] , который можно обозначить Y*.
В строгом языке программирования комбинатор Y будет расширяться до переполнения стека или никогда не останавливаться в случае оптимизации хвостового вызова . [11] Комбинатор Z будет работать в строгих языках (также называемых жадными языками, где применяется аппликативный порядок оценки). У комбинатора Z следующий аргумент определен явно, предотвращая расширение в правой части определения: [12]
а в лямбда-исчислении это эта-расширение комбинатора Y :
Если F — комбинатор с фиксированной точкой в нетипизированном лямбда-исчислении, то мы имеем
Термины, которые имеют то же дерево Бёма , что и комбинатор с фиксированной точкой, т.е. имеют то же бесконечное расширение , называются нестандартными комбинаторами с фиксированной точкой . Любой комбинатор с фиксированной точкой также является нестандартным, но не все нестандартные комбинаторы с фиксированной точкой являются комбинаторами с фиксированной точкой, потому что некоторые из них не удовлетворяют уравнению с фиксированной точкой, которое определяет «стандартные». Такие комбинаторы называются строго нестандартными комбинаторами с фиксированной точкой ; примером является следующий комбинатор:
где
Множество нестандартных комбинаторов с фиксированной точкой не является рекурсивно перечислимым . [6]
Комбинатор Y — это частная реализация комбинатора с фиксированной точкой в лямбда-исчислении. Его структура определяется ограничениями лямбда-исчисления. Использовать эту структуру при реализации комбинатора с фиксированной точкой в других языках необязательно и нецелесообразно.
Ниже приведены простые примеры комбинаторов с фиксированной точкой, реализованных в некоторых парадигмах программирования .
В языке, поддерживающем ленивые вычисления , например, в Haskell , можно определить комбинатор с фиксированной точкой, используя определяющее уравнение комбинатора с фиксированной точкой, которое обычно называется fix
. Поскольку в Haskell есть ленивые типы данных, этот комбинатор также можно использовать для определения фиксированных точек конструкторов данных (а не только для реализации рекурсивных функций). Определение приведено здесь, а затем приведены некоторые примеры использования. В Hackage исходный пример: [13]
fix , fix' :: ( a -> a ) -> a fix f = let x = f x in x -- Лямбда удалена. Совместное использование. -- Исходное определение в Data.Function. -- альтернатива: fix' f = f ( fix' f ) -- Лямбда удалена. Несовместное использование. исправить ( \ x -> 9 ) — это вычисляется как 9 fix ( \ x -> 3 : x ) — возвращает ленивый бесконечный список [3,3,3,...] fact = fix fac — вычисляется как факториальная функция , где fac f 0 = 1 fac f x = x * f ( x - 1 ) факт 5 -- оценивается как 120
В строгом функциональном языке, как показано ниже с помощью OCaml , аргумент f раскрывается заранее, что приводит к бесконечной последовательности вызовов,
Эту проблему можно решить, определив fix с дополнительным параметром.
пусть rec fix f x = f ( fix f ) x (* обратите внимание на дополнительный x; здесь fix f = \x-> f (fix f) x *)пусть factabs факт = функция (* factabs имеет дополнительный уровень лямбда-абстракции *) 0 -> 1 | x -> x * факт ( x - 1 )пусть _ = ( исправить факты ) 5 (* вычисляется как "120" *)
В многопарадигмальном функциональном языке (украшенном императивными функциями), таком как Lisp , Питер Ландин предложил использовать присваивание переменной для создания комбинатора с фиксированной точкой [14] , как в следующем примере с использованием Scheme :
( define Y! ( lambda ( f ) (( lambda ( i ) ( set! i ( f ( lambda ( x ) ( i x )))) ;; (set! i expr) присваивает i значение expr i ) ;; заменяя его в текущей лексической области #f )))
Используя лямбда-исчисление с аксиомами для операторов присваивания, можно показать, что Y!
удовлетворяет тому же закону фиксированной точки, что и комбинатор Y с вызовом по значению: [15] [16]
В более идиоматичном современном использовании Lisp это обычно осуществляется с помощью лексически ограниченной метки ( let
выражения), поскольку лексическая область действия не была введена в Lisp до 1970-х годов:
( define Y* ( lambda ( f ) (( lambda ( i ) ( let (( i ( f ( lambda ( x ) ( i x ))))) ;; (let ((i expr)) i) локально определяет i как expr i )) ;; нерекурсивно: таким образом, i в expr не является expr #f )))
Или без внутренней этикетки:
( определить Y ( лямбда ( f ) (( лямбда ( i ) ( i i )) ( лямбда ( i ) ( f ( лямбда ( x ) ( применить ( i i ) x )))))))
Этот пример представляет собой слегка интерпретативную реализацию комбинатора с фиксированной точкой. Класс используется для хранения функции fix , называемой fixer . Функция, которая должна быть исправлена, содержится в классе, который наследует от fixer. Функция fix обращается к функции, которая должна быть исправлена, как к виртуальной функции. Что касается строгого функционального определения, fix явно задается дополнительный параметр x , что означает, что ленивая оценка не нужна.
шаблон < имя_типа R , имя_типа D > class fixer { public : R fix ( D x ) { return f ( x ); } private : virtual R f ( D ) = 0 ; }; факт класса : публичный фиксер < long , long > { virtual long f ( long x ) { if ( x == 0 ) { return 1 ; } return x * fix ( x -1 ); } }; длинный результат = факт (). исправить ( 5 );
В системе F (полиморфное лямбда-исчисление) полиморфный комбинатор с фиксированной точкой имеет тип [17]
где a — переменная типа . То есть, fix принимает функцию, которая отображает a → a, и использует ее для возврата значения типа a.
В простом типизированном лямбда-исчислении, расширенном рекурсивными типами данных , операторы с фиксированной точкой могут быть записаны, но тип «полезного» оператора с фиксированной точкой (того, применение которого всегда возвращает) может быть ограничен.
В простом типизированном лямбда-исчислении комбинатору с фиксированной точкой Y не может быть назначен тип [18] , поскольку в какой-то момент он будет иметь дело с подтермом самоприменения по правилу применения:
где имеет бесконечный тип . Фактически, ни один комбинатор с фиксированной точкой не может быть типизирован; в этих системах любая поддержка рекурсии должна быть явно добавлена в язык.
В языках программирования, поддерживающих рекурсивные типы данных , можно типизировать комбинатор Y, соответствующим образом учитывая рекурсию на уровне типа. Необходимость самостоятельного применения переменной x может быть решена с помощью типа ( Rec a
), который определен так, чтобы быть изоморфным ( Rec a -> a
).
Например, в следующем коде Haskell мы имеем In
и out
— имена двух направлений изоморфизма с типами: [19] [20]
Вход :: ( Рецепт а -> а ) -> Рецепт а выход :: Рецепт а -> ( Рецепт а -> а )
что позволяет нам записать:
новыйтип Rec a = In { out :: Rec a -> a } y :: ( a -> a ) -> a y = \ f -> ( \ x -> f ( out x x )) ( In ( \ x -> f ( out x x )))
Или эквивалентно в OCaml:
тип ' a recc = In of ( ' a recc - > ' a ) выпустить ( In x ) = x пусть y f = ( fun x a -> f ( out x x ) a ) ( In ( fun x a -> f ( out x x ) a ))
Альтернативно:
пусть y f = ( fun x -> f ( fun z -> out x x z )) ( In ( fun x -> f ( fun z -> out x x z )))
Поскольку комбинаторы с фиксированной точкой могут использоваться для реализации рекурсии, их можно использовать для описания определенных типов рекурсивных вычислений, таких как итерации с фиксированной точкой , итеративные методы , рекурсивное соединение в реляционных базах данных , анализ потока данных , наборы нетерминалов FIRST и FOLLOW в контекстно-свободной грамматике , транзитивное замыкание и другие типы операций замыкания .
Функция, для которой каждый вход является фиксированной точкой, называется функцией тождества . Формально:
В отличие от универсальной квантификации по всем , комбинатор с фиксированной точкой строит одно значение, которое является фиксированной точкой . Замечательным свойством комбинатора с фиксированной точкой является то, что он строит фиксированную точку для произвольной заданной функции .
Другие функции имеют особое свойство: после однократного применения последующие применения не оказывают никакого эффекта. Более формально:
Такие функции называются идемпотентными (см. также Проекция (математика) ). Примером такой функции является функция, которая возвращает 0 для всех четных целых чисел и 1 для всех нечетных целых чисел.
В лямбда-исчислении , с вычислительной точки зрения, применение комбинатора с фиксированной точкой к функции тождества или идемпотентной функции обычно приводит к невыполняющемуся вычислению. Например, мы получаем
где результирующий член может быть сведен только к самому себе и представляет собой бесконечный цикл.
Комбинаторы с фиксированной точкой не обязательно существуют в более ограничивающих моделях вычислений. Например, они не существуют в простом типизированном лямбда-исчислении .
Комбинатор Y позволяет определить рекурсию как набор правил перезаписи [21] , не требуя при этом собственной поддержки рекурсии в языке. [22]
В языках программирования, поддерживающих анонимные функции , комбинаторы с фиксированной точкой позволяют определять и использовать анонимные рекурсивные функции , т. е. без необходимости привязывать такие функции к идентификаторам . В этой ситуации использование комбинаторов с фиксированной точкой иногда называется анонимной рекурсией . [примечание 4] [23]