Рекурсивные функции и лямбда исчисление а черча
Ля́мбда-исчисле́ние (λ-исчисление, лямбда-исчисление) — формальная система, разработанная американским математиком Алонзо Чёрчем, для формализации и анализа понятия вычислимости.
λ-исчисление может рассматриваться как семейство прототипных языков программирования. Их основная особенность состоит в том, что они являются языками высших порядков. Тем самым обеспечивается систематический подход к исследованию операторов, аргументами которых могут быть другие операторы, а значением также может быть оператор. Языки в этом семействе являются функциональными, поскольку они основаны на представлении о функции или операторе, включая функциональную аппликацию и функциональную абстракцию.
λ-исчисление реализовано Джоном Маккарти в языке Лисп. В начале реализация идеи λ-исчисления была весьма громоздкой. Но по мере развития Лисп-технологии (прошедшей этап аппаратной реализации в виде Лисп-машины) идеи получили ясную и четкую реализацию.
Чистое λ-исчисление
Это простейший из семейства прототипных языков программирования, чистое λ-исчисление, термы которого, называемые также объектами (обами), или λ-термами, построены исключительно из переменных применением аппликации и абстракции. Изначально наличия каких-либо констант не предполагается.
Аппликация и абстракция
В основу λ-исчисления положены две фундаментальные операции: аппликация и абстракция. Аппликация означает применение или вызов функции по отношению к заданному значению. Её обычно обозначают , где f — функция, а a — значение. Это соответствует общепринятой в математике записи f(a), которая тоже иногда используется, однако для λ-исчисления важно то, что f трактуется как алгоритм, вычисляющий результат по заданному входному значению. В этом смысле аппликация f к a может рассматриваться двояко: как результат применения f к a, или же как процесс вычисления . Последняя интерпретация аппликации связана с понятием β-редукции.
Абстракция или λ-абстракция в свою очередь строит функции по заданным выражениям. Именно, если — выражение, свободно содержащее x, тогда запись означает: λ функция от аргумента x, которая имеет вид t[x]) обозначает функцию . Таким образом, с помощью абстракции можно конструировать новые функции. Требование, чтобы x свободно входило в t, не очень существенно — достаточно предположить, что , если это не так.
β-редукция
Поскольку выражение обозначает функцию, ставящую в соответствие каждому x значение , то для вычисления выражения
,
в которое входят и аппликация и абстракция, необходимо выполнить подстановку числа 3 в терм вместо переменной x. В результате получается . Это соображение в общем виде записывается как
η-преобразование
η-преобразование выражает ту идею, что две функции являются идентичными тогда и только тогда, когда, будучи применённые к любому аргументу, дают одинаковые результаты. η-преобразование переводит друг в друга формулы и f (в обратную сторону — только если x не имеет свободных вхождений в f: иначе свободная переменная x после преобразования станет связанной внешней абстракцией).
Каррирование (карринг)
Семантика бестипового λ-исчисления
Тот факт, что термы λ-исчисления действуют как функции, применяемые к термам λ-исчисления (то есть, возможно, к самим себе), приводит к сложностям построения адекватной семантики λ-исчисления. Можно ли приписать λ-исчислению какой-либо смысл? Желательно иметь множество D, в которое вкладывалось бы его пространство функций D → D. В общем случае такого D не существует по соображениям ограничений на мощности этих двух множеств, D и функций из D в D: второе имеет большую мощность, чем первое.
Эту трудность преодолел Д. С. Скотт, построив понятие области D (полной решётки [1] или, более обще, полного частично упорядоченного множества со специальной топологией) и урезав D → D до непрерывных (в имеющейся топологии) функций [2] . После этого также стало понятно, как можно строить денотационную семантику языков программирования. Это произошло благодаря тому, что с помощью конструкций Скотта можно придать значение также двум важным конструкциям языков программирования — рекурсии и типам данных.
Лямбда-исчисление было изобретено в начале 30-х годов логиком А. Черчем, который использовал его в качестве формализма для обоснования математики.
В настоящее время лямбда-исчисление является основной формализацией, применяемой в исследованиях связанных с языками программирования. Это, связано, со следующими факторами:
• Это единственная формализация, которая может быть непосредственно использована для написания программ.
• Лямбда-исчисление дает простую и естественную модель для таких
важных понятий, как рекурсия и вложенные среды.
• Большинство конструкций традиционных языков программирова
ния может быть отображено в конструкции лямбда-исчисления.
• Функциональные языки являются удобной формой синтаксической записи для конструкций различных вариантов лямбда-исчисления. Некоторые современные языки например Haskell имеют полное соответствие своей семантики с семантикой подразумеваемых конструкций лямбда-исчисления.
Лямбда-выражением будем называть конструкцию вида
где Е — некоторое выражение, возможно, использующее переменную х. Пример.λх.х 2 представляет собой функцию, возводящую свой аргумент в квадрат.
Использование лямбда-нотации позволяет четко разделить случаи, когда под выражением вида f(x) мы понимаем саму функцию f , а когда ее значение в точке х.
Существует операция каррирования, позволяющая записывать функции многих переменных в обычной лямбда-нотации. Она заключается в том, чтобы выражения вида λх у.х + у рассматривать как функцию , т.е. если его применить к одному аргументу, результатом будет функция, которая принимает другой аргумент. Таким образом:
(λх у.х + у)12 = (λу.1 + у) 2 = 1 + 2.
В лямбда исчислении выражения λх.Е[х]и λу.Е[у]считаются эквивалентными (это называется α-эквивалентностью, и процесс преобразования между такими парами называют α-преобразованием). При этом, необходимо наложить условие, что у не является свободной переменной в Е[х].
Как известно, теоретические основы императивного программирования были заложены ещё в 30-х годах XX века Аланом Тьюрингом и Джоном фон Нейманом. Теория, положенная в основу функционального подхода, также родилась в 20-х — 30-х годах. В числе разработчиков математических основ функционального программирования можно назвать Мозеса Шёнфинкеля (Германия и Россия) и Хаскелла Карри (Англия), разработавших комбинаторную логику, а также Алонзо Чёрча (США), создателя l-исчисления.
Теория так и оставалась теорией, пока в начале 50-х прошлого века Джон МакКарти не разработал язык Lisp, который стал первым почти функциональным языком программирования и на протяжении многих лет оставался единственным таковым. Хотя Lisp всё ещё используется (как, например, и FORTRAN), он уже не удовлетворяет некоторым современным запросам, которые заставляют разработчиков программ взваливать как можно большую ношу на компилятор, облегчив тем самым свой непосильный труд. Необходимость в этом, конечно же, возникла из-за всё более возрастающей сложности программного обеспечения.
В связи с этим обстоятельством всё большую роль начинает играть типизация. В конце 70-х — начале 80-х годов XX века интенсивно разрабатываются модели типизации, подходящие для функциональных языков. Большинство этих моделей включали в себя поддержку таких мощных механизмов как абстракция данных и полиморфизм. Появляется множество типизированных функциональных языков: ML, Scheme, Hope, Miranda, Clean и многие другие. Вдобавок постоянно увеличивается число диалектов.
В основе функционального программирования лежит идея, что в результате каждого действия возникает значение. Значения становятся аргументами следующих действий, и конечный результат всей задачи выдается пользователю. Программа строится из логически расчлененных определений функций, которые состоят из организующих вычисления управляющих структур и из вложенных, часто вызывающих самих себя (рекурсивных) вызовов функций.
Основными средствами функционального программирования как раз и являются композиция и рекурсия. Ни ячеек памяти, ни операторов присваивания, ни циклов, ни, тем более, блок-схем, ни передач управления.
В основе функционального программирования лежит строгий математический аппарат лямбда-исчисления Чёрча и теория рекурсивных функций.
Введенное в 1931 году математиком Алонзо Черчем, лямбда-исчисление оперирует всего тремя типами элементов:
• символами, представляющими переменные и константы;
• скобками для группировки символов;
• обозначениями функций с использованием греческой буквы лямбда.
Лямбда-исчисление используется для синтаксического описания свойств математических функций и обработки их в качестве правил.
Функциональная программа состоит из совокупности определений функций. Функции, в свою очередь, представляют собой вызовы других функций и предложении, управляющих последовательностью вызовов.
Вычисления начинаются с вызова некоторой функции, которая в свою очередь вызывает функции, входящие в состав ее определения и т. д. в соответствии с иерархией определений и структурой условных предложений. Функции могут прямо или опосредованно вызывать сами себя. Каждый вызов возвращает некоторое значение в вызвавшую его функцию, вычисление которой после этого продолжается. Процесс вычислений повторяется до тех пор, пока запустившая вычисления функция не вернет конечный результат пользователю. В функциональном программировании нет места присваиванию и передаче управления. Разветвление вычислений основано на механизме обработки аргументов условного предложения. При таком подходе к программированию рекурсия является единственным способом организации повторяющихся вычислений.
В функциональной программе не должно быть:
• функций с побочными эффектами.
Этот перечень следует из основного свойства функционального программирования – прозрачности по ссылкам.
В основе функционального программирования лежит идея, что в результате каждого действия возникает значение. Значения становятся аргументами следующих действий, и конечный результат всей задачи выдается пользователю. Программа строится из логически расчлененных определений функций, которые состоят из организующих вычисления управляющих структур и из вложенных, часто вызывающих самих себя (рекурсивных) вызовов функций.
Основными средствами функционального программирования как раз и являются композиция и рекурсия. Ни ячеек памяти, ни операторов присваивания, ни циклов, ни, тем более, блок-схем, ни передач управления.
В основе функционального программирования лежит строгий математический аппарат лямбда-исчисления Чёрча и теория рекурсивных функций.
Введенное в 1931 году математиком Алонзо Черчем, лямбда-исчисление оперирует всего тремя типами элементов:
• символами, представляющими переменные и константы;
• скобками для группировки символов;
• обозначениями функций с использованием греческой буквы лямбда.
Лямбда-исчисление используется для синтаксического описания свойств математических функций и обработки их в качестве правил.
Функциональная программа состоит из совокупности определений функций. Функции, в свою очередь, представляют собой вызовы других функций и предложении, управляющих последовательностью вызовов.
Вычисления начинаются с вызова некоторой функции, которая в свою очередь вызывает функции, входящие в состав ее определения и т. д. в соответствии с иерархией определений и структурой условных предложений. Функции могут прямо или опосредованно вызывать сами себя. Каждый вызов возвращает некоторое значение в вызвавшую его функцию, вычисление которой после этого продолжается. Процесс вычислений повторяется до тех пор, пока запустившая вычисления функция не вернет конечный результат пользователю. В функциональном программировании нет места присваиванию и передаче управления. Разветвление вычислений основано на механизме обработки аргументов условного предложения. При таком подходе к программированию рекурсия является единственным способом организации повторяющихся вычислений.
В функциональной программе не должно быть:
• функций с побочными эффектами.
Этот перечень следует из основного свойства функционального программирования – прозрачности по ссылкам.
Прозрачность по ссылкам – необходимое условие функциональной программы
Прозрачность по ссылкам (функциональность) является фундаментальным свойством математических функций и может быть сформулировано следующим образом: значение функции зависит только от нее самой и аргументов вызова. Это означает, что каждое выражение определяет единственную величину, которую нельзя изменить ни путем ее вычисления, ни предоставлением различным частям программы возможности совместно использовать это выражение.
Ссылки на некоторую величину эквивалентны самой величине, и возможность ссылаться на некоторое выражение из другой части программы не влияет на значение этого выражения.
Большинство императивных языков позволяют функции в процессе своего выполнения читать и изменять значения глобальных переменных. Такие функции называются функциями с побочными эффектами. Поэтому, если вызывать одну и ту же функцию несколько раз с одним и тем же аргументом, можно в результате получать различные результаты.
В функциональном программировании оператор присваивания отсутствует, объекты нельзя изменять и уничтожать, можно только создавать новые путем декомпозиции и синтеза существующих объектов. О ненужных объектах позаботится встроенный в язык сборщик мусора (удаление ненужных объектов программы вынесено на уровень системы). Благодаря этому в функциональных языках все функции свободны от побочных эффектов.
Из отсутствия побочных эффектов следует еще одно преимущество – параллелизм вычислений. На интуитивном уровне понятно, что если все функции для вычислений используют только свои параметры, можно вычислять независимые функции в произвольном порядке или, скажем, параллельно, на результат вычислений это не повлияет. Причем параллелизм этот может быть организован как на уровне компилятора языка, так и на уровне архитектуры. В лямбда-исчислении доказывается теорема, которая подтверждает это интуитивное соображение на уровне математической теории.
Функциональным языкам присущи энергичный и ленивый виды вычислений.
В большинстве императивных языков программирования при применении функции к аргументу сначала вычисляется аргумент, а затем уже передается функции. В этом случае говорят, что аргумент передается по значению, подразумевая при этом, что только его значение передается в тело функции. Такое правило вычислений или механизм вызова называется вызовом по значению. Его преимущество заключается в том, что реализация достаточно проста: сначала вычисляется аргумент, а затем вызывается функция. Недостатком является избыточное вычисление, когда в некоторых случаях значение аргумента не требуется вызываемой функции.
Противоположным вызову функции по значению является вызов по необходимости, при котором все аргументы передаются функции в невычисленном виде и вычисляются только тогда, когда в них возникает необходимость внутри тела функции. Преимущество вызова по необходимости состоит в отсутствии лишних вычислений, если значение аргумента не понадобится. Его недостаток более сложная, по сравнению с вызовом по значению, реализация системы программирования. В этом случае функциям передаются не значения тех или иных параметров, а выражения, которые при определенных условиях придется вычислять и применять уже в теле функции. Вызов по необходимости не характерен для императивных языков программирования и многих функциональных языков, а функциональные языки, поддерживающие вызов по необходимости, в большинстве своем снабжены конструкторами, обеспечивающими вычисление по схеме вызов по значению.
В теории функционального программирования принято говорить о двух крайних видах вычисления: энергичном и ленивом. Эти варианты выделяются, так как представляют крайние случаи, хотя существуют и другие варианты.
Ленивое вычисление – стратегия планирования, при которой вычисления не выполняются до тех пор, пока не возникнет необходимость в их результатах.
Если функциональный язык не поддерживает отложенные вычисления, то он называется строгим (strict) языком программирования, в отличие от нестрогого (lazy) языка, с ленивой стратегией вычисления.
исчисление – это безтиповая теория, которая рассматривает функции, как правила, а не как графики. В традиционном подходе: два представлениявыражают одну и ту же функцию, а с точки зренияисчисления они выражают разные функции, так как правила вычисления различны. исчисление – это прикладное исчисление предикатов 1-го порядка (ПИП 1-го порядка).
ОСОБЕННОСТИ:
Безтиповость: объекты могут являться функциями и аргументами, т.е. функции могут быть заданы программами (функции или процедуры Pascal), которые могут передаваться через формальные параметры другим программам.
исчисление являетсятеоретической моделью современного функционального программирования (например, язык ЛИСП, который используетисчисление в качестве промежуточного кода после 1-го этапа трансляции).
2.3.1. выражения и их вычисления.
Пример: . Висчислении различают (устраняют различное понимание): символьную записьи ее вызов (вычисление).
исчисление изучает функции и их аппликативное поведение (поведение относительно применения к аргументу). Выражениеможно считать как функцию от(это записывается как) и как функцию от(это записывается как). Вызовы могут быть следующими:
а) и;
б) ;
в) .
2.3.2. Определение термов ивыражений
ОПРЕДЕЛЕНИЕ:
Каждая переменная или константа есть терм.
По любой переменной и любомутерму M строится новыйтерм:– функция от, которую называютабстракцией.
Замечание 2:Константы: целые числа, булевы константы, арифметические операции (функторы), булевы функции и т.п.
По любым термам M и N строится новыйтерм MN, обозначающий применение (аппликацию) оператора M к аргументу N, то есть подстановкапозволяет получать выражения (предикаты).
Замечание 4: Правила вывода определяют алгоритм вычисления выражений
СВОРАЧИВАНИЕ,СВОРАЧИВАНИЕ И РЕДУКЦИЯ
Определение 1:Константа, обозначающая функтор, применяемый к операндам, определяет подтерм, называемыйредексом, а процесс применения называетсясворачиванием, в результате которого получается новое выражение.
Определение 2: Терм вида называетсяредексом.
Определение 3: Еслиредекс содержится в термеи одно из его вхождений заменяется термом (подстановкой), то этот процесссворачивания (свертывания) обозначаетсяи означает, что термсворачивается к выражению.
Читайте также: