Путешествия по миру компьютерной науки, часть 1В
Основы Функционального программирования Лоренс Ц. Паулсон Компьютерная...
47 downloads
123 Views
1MB Size
Report
This content was uploaded by our users and we assume good faith they have the permission to share this book. If you own the copyright to this book and it is wrongfully on our website, we offer a simple DMCA procedure to remove your content from our site. Start by pressing the button below!
Report copyright / DMCA form
Путешествия по миру компьютерной науки, часть 1В
Основы Функционального программирования Лоренс Ц. Паулсон Компьютерная лаборатория Университет Кембридж
Авторские права 1995 Лоренс Ц. Паулсон
Содержание 1. 2. 3. 4. 5. 6. 7. 8.
Вступление Равенство и нормализация Кодирования данных в λ-вычислениях Написание рекурсивных функций в λ-вычислениях λ-вычисления и теория вычислений ISWIM: λ-вычисления как язык программирования Ленивые вычисления, используя комбинаторы Методы компиляции, использующие комбинаторы
1 Введение Этот курс связан с λ-вычислениями и близкой к ним наукой – комбинаторной логикой. λ-вычисления важны для функционального программирования и особенно для компьютерной науки по следующим причинам: 1. Может быть смоделированы связанные переменные и область применения в структурированных языках 2. Некоторые функции, называемые вызывающие механизмы – вызов по имени, вызов по значению и вызов по требования – могут быть смоделированы. Последние 2 вызова также известны как строгие вычисления и ленивые вычисления. 3. λ-вычисления является универсальной по признаку Тьюринга, и, наверное, самая натуральная модель вычислений. Тезис Чёрча утверждает, что вычислимые функции являются теми, которые могут быть представлены в λ-вычислениях. 4. Все обычные структуры функционального программирования, включая бесконечные списки, могут быть представлены. Вычисления конечных объектов может быть определено формально в λ-вычислениях. 5. Их идея о слиянии (свойство Чёрча-Россера), завершении и нормальных формах обычно используется в переписывании теорий. 6. LISP, один из главных функциональных языков, был создан благодаря λвычислениям. Многие функциональные языки, такие как ML, состоят из чуть более того, что λ-вычисления с добавочным синтаксисом. 7. Два основных реализационных метода: SECD машина (для строгих вычислений) и комбинаторная редукция (для ленивых вычислений) используют λ-вычисления. 8. λ-вычисления и их дополнение могут быть использованы для создания более совершенных типов систем, таких как полиморфизм, и для исследования теоретических разногласий, таких как синтез программы. 9. Детонационная семантика, которая является очень важным методом для формального определения формальных языков, использует λ-вычисления в своей нотации. Книга Хиндлера и Селдина[7] является кратким вступлением в λ-вычисления и комбинаторы. Книга Гордона[6] ориентирована в основном на компьютерную науку, частично совпадает с этим курсом. Книга Барендредта[1] является последним словом в науке λ-вычислений. Признательность. Рубен Томас обратил внимание на множество ошибок в предыдущей версии этого издания.
1.1 λ-вычисления Около 1924 году, Шолфинг создал простую теорию функций. В 1934, Чёрч представил λвычисления и использовал их, чтобы разработать формальную теорию множеств, которая впоследствии оказался противоречивой. Более удачно он использовал λ-вычисления для формализации синтаксиса Вайтведа и массивного Принципа Математики Расселяя. В 1940х, Хаскель Б. Карри представил комбинаторную логику, теория свободных функций. Позднее, Роджер Хиндлей разработал, что сейчас называется вывод типа. Робин Милнер использовал это для разработки полиморфный тип системы языка ML, и опубликовал доказательство, что хорошо разработанная система не может страдать от ошибок типов во время работы. Дана Скот разработал модели λ-вычисления. Вместе с теорией доменов, он и Кристофер Вадсворд разработали графическую редукцию как метод для вычисления ленивых λ-вычислений. Давид Турнер применил графическую редукцию к комбинаторам, что привело к результативному выполнению ленивых λ-вычислений. Определение 1 Термы λ-вычислений, называемые λ-термами, являются рекурсивной формой данного множества переменных x,y,z, … Они могут принимать одну из следующих форм: x (λx.M) (MN)
переменная абстракция, где M является термом абстракция, где M и N являются термами
Мы используем заглавные буквы, например L,M,N,…, для термов. Под M≡N понимается, что N и M идентичные λ-термы. Равенство между термами, M=N, мы обсудим чуть позже. 1.2 Связанные переменные и замены В (λx.M), x будем называть связанной переменной и M тело. Каждое вхождение x в M связано абстракцией. Вхождение переменной называют свободным, если переменная не связана окружающей абстракцией. Например, x входит связано и y входит свободно в (λz.(λx.(yx)). Нотация вовлекающие свободные и связанные переменные существуют повсюду в b математики. Рассмотрим интеграл ∫ f(x)dx, где x является связанной, и продукцию a
∏
n k=0
p(k), где k является связанной. Кванторы V и ] так же являются связанными
переменными. Абстракция (λx.M) предназначена для представления функции f так, что f(x) = M для всех x. Применяя f к N, вырабатывается результат подстановки N на все свободные вхождения x в M. Например: (λx.x) Функция тождественности, которая возвращает свой аргумент. Обычно она называется I (λy.x) Функция константы, которая возвращает x при применении любого аргумента Давайте определим эти понятия. Определение 2 BV (M), множество всех связанных переменных в M, определяется как:
Определение 3 FV (M), множество всех свободных переменных в M, определяется как:
Определение 4 M [L/y], результат подстановки L во все вхождения y в M, определяется как
Нотации определенные выше сами по себе не входят в λ-вычисления. Они принадлежат метаязыку: они говорят о λ-вычислениях. 1.3 Уклонение от захвата переменных при подстановках Подстановки не должны перемешивать связанные переменные. Рассмотрим терм . Это должно показывать, что при применении аргумента N, функция должна возвращать функцию константы
. К сожалению, это не работает при
; мы
определили подстановку таким образом, что . Замена x на y в константной функции делает её функцией тождества. Свободное вхождение x превращается в связанное вхождение y – это пример захвата переменных. Если бы это позволялось делать, то λ-вычисления были бы противоречивыми. Подстановка является безопасной для связанных переменных в M, и она является взаимоисключающей для свободных переменных в N: Мы всегда можем переименовать связанные переменные в M, если потребуется, чтобы удовлетворять вышесказанному правилу. В приведённом примере ранее, мы можем заменить на и потом получить исправленную подстановку
; результат всегда будет функцией константы.
1.4 Преобразования Идея о том, что λ-вычисления формулируют функции, формально выражено через правила преобразования. Существуют α-преобразования, β-преобразования и ηпреобразования. α-преобразование переименует связанную переменную в абстракции с x на y. Это верно, если y не входит (не является свободной переменной) в M. Например: Мы должны обычно игнорировать различие между термами, которые могут сделаны идентичными, выполняется α-преобразование.
β-преобразование подставляет аргумент, N, в тело абстракции, M. Это верно, когда Например, Ещё пример: η-преобразование упрощает простую функцию (λx.(Mx) до M. Это верно, когда Таким образом, M не зависит от x; абстракция делает ничего кроме как применение M к своим аргументам. Например: Обратите внимание, что функции (λx.(Mx) и M всегда возвращают один и тот же ответ , (MN), когда применить к любому аргументу N. Правило η-преобразования воплощает в себе принцип экстенциональности: 2 функции эквивалентны, если они всегда возвращают одинаковые результат при одинаковых значениях аргументов. 1.5 Редукции Мы говорим, что , или M сводится к N, если или (Так как αпреобразование не являются направленными, и не очень интересными, то мы обычно игнорируем их). Редукция может состоят из применений преобразований к некоторому подтерму M для того, чтобы создать N. Более формально, мы можем предоставить правила вывода:
Если к терму нельзя применить редукцию, тогда он представлен в нормальной форме. Например, λxy.y и xyz представлены в нормальной форме. Нормировать терм значит применять редукцию, пока не получим нормальной формы. Терм имеет нормальную форму, если он может быть редуцирован до этой нормальной формы. Нпример, (λx.x)y не является нормальной формой терма, но имеет нормальную форму y. Многие λ-термы не могут быть редуцированы к нормальной форме. Например, (λx.xx)(λx.xx) редуцируется сам в себя по β-преобразованию. Хотя к нему и была применена редукция, но он, очевидно, не является нормальной формой. Этот терм обычно называется Ω. 1.6 Каррирование функций λ-вычисления имеют функции только 1 переменной. Функция многих переменных представлена в виде функции, чей результат является другой функцией. Например, предположим, что L является термом, содержащим только x и y как свободные переменные, и мы хотим формализовать функцию f(x,y) = L. Абстракция (λy.L) содержит x как свободную; для каждого x она становится функцией от у. Абстракция (λx.(λy.L)) не содержит сводных переменных; когда она применяется с аргументами M и N, то результат получается заменой x на M и y на N в L. Символически, мы делаем 2 β-преобразования. ( все необходимые α-преобразование опущены): Этот приём известен как каррирование в честь Хаскеля Б. Карри, и функция, использующая вложенные λ-выражения называется каррированая функция. На самом деле, это было впервые предложено Шонфинкелем. Понятно, что это работает для любого числа аргументов. Каррированные функции являются популярными в функциональном программировании, потому что они могут быть применены к их первым аргументам, возвращая функции, которые полезны сами для себя.
1.7 Выставление скобок (соглашение о скобках) Сокращение вложенных абстракций и приложений сделают каррированные функции проще для написания. Мы будем сокращать:
Мы будем опускать внешние скобки и те, что окружают тело абстракции. Например:
Это очень важно понять, как работают скобки. Мы имеем редукцию: но похожий терм λz.z(λx.M) не подвергается редукции, кроме той, что происходит внутри M и N, так как λx.M не применяется ни к чему. Вот ещё один пример того, как применение к каррированной функции выглядит с опущенными скобками: Заметьте, что λx.MN интерпретируется как λx.(MN), а не как (λx.M)N. Так же xyz интерпретируется как (xy)z, а не x(yz). Упражнение 1 Что произойдёт в редукционном выводе (λxy.L)MN, если y является свободной в M? Упражнение 2 Дайте 2 примера редукционного вывода, который начинается с (λx.(λy.xy)z)y и заканчивается нормальной формой. (Эти формы должны быть идентичными: смотри ниже).
2 Равенство и нормализация λ-вычисления является математической теорией: она состоит из правил доказательства, что 2 λ-терма эквивалентны. Главным является то, что 2 терма являются эквивалентными, если они могут быть сведены к одному и тому же терму. 2.1 Многоуровневая редукция Строго говоря, значит, что M редуцируется (сводится) к N ровно в 1 шаг редукции, возможно применяя M к его подтерму. Часто, мы заинтересованы узнать может , если ли M быть редуцировано к N за любое число шагов. Мы пишем
Например, ((λz.(zy))( λx.x)) у. Заметьте, что транзитивное замыкание .
есть отношение
, рефлексивно-
2.2 Эквивалентность между λ-термами Неформально можно написать M = M’ , если M может быть преобразовано в M’, делая 0 или более редукций и расширений ( расширение – это обратное к редукции, например: ) Типичная ситуация показана тут:
Например, a((λy.by)c) = (λx.ax)bc потому, что обе стороны можно свести к a(bc). Заметьте, что отношение = является отношением , минимальное эквивалентное отношение, содержащие . Интуитивно ясно, что M=M’ означает, что M и M’ имеют один и тот же значение (ценность). Эквивалентность, как определено здесь, удовлетворяет всем стандартным свойствам. Во-первых, это отношение эквивалентно – оно удовлетворяет законы рефлективности, симметрии и ассоциативности:
А так же оно удовлетворяет законы сравнения для каждого способа построения λ-терма.:
Последнее 6-ое свойство, показанное выше можно легко проверить, построив соответствующие диаграммы для каждого равенства. Эти последние 2 равенства предполагают, что 2 терма будут эквивалентны, если мы построим их по одинаковому пути, начиная с эквивалентных термов. Другими словами, если M = M’, то, меняя M на M’ в терме получаем эквивалентный терм. Определение 5 Равенство λ-термов является минимальным отношением, удовлетворяющим 6 правил, которые были приведены выше. 2.3 Теорема Чёрча-Россера Эта фундаментальная теорема утверждает, что редукция в λ-вычислениях является вырожденной: не найдётся 2-х разных последовательностей редукции, начинающихся с одинакового λ-терма, которые бы редуцировались в 2 разные нормальные формы. Нормальная форма терма независима от порядка, в котором происходит редукция. Теорема 6(Чёрча-Россера) Если M = N, тогда существует L, такое что
и
.
Доказательство Смотри Барендредта[1] или Хиндлера и Селдина[7]. Например, (λx.ax)(( λy.by)c) имеет 2 разные последовательности шагов редукции, обе ведущие к одинаковой нормальной форме. Подтерм, над которым оказываются какие-то действия, подчеркнут:
Теорема имеет несколько важных последствий: • Если M = N и N находится в нормальной форме, тогда ; если терм может быть трансформирован в нормальную форму, используя редукцию и расширение, то нормальную форму можно получить, используя только редукцию
•
Если M = N, где оба терма находятся в нормальной форме, тогда M ≡ N (при условии переименование связанных переменных). Обратно: если M и N находятся в нормальных формах и являются различными, тогда ; нет никакого способа . трансформировать M в N. Например,
Теория равенств была бы несовместимой, если все равенства доказуемыми. Благодаря теореме Чёрча-Росселя, мы знаем, что λ-вычисления является совместимой. Нет ни малейшего шанса, что мы сможем получить 2 различные нормальные формы, идя по разным стратегиям редукции. Без этого свойства, λ-вычисления были бы малозначимы для вычислений. 2.4 Свойство ромба Основным действием в доказательстве теоремы Чёрча-Россера является продемонстрировать свойство ромба: если и , то существует терм L, такой что и . На диаграмме это выглядит так:
Свойство ромба очень важно: оно утверждает, что, не смотря на сколько отдалённо друг от друга, мы делаем редукцию 2-мя разными стратегиями редукции, всегда возможно придти к одному и тому же в дальнейшем процессе редукции. Что касается теоремы Чёрча-Россера, то посмотрите снова на диаграмму M = M’ и заметьте, что мы можем мы можем выложить эту структуру как по кирпичикам, склеивая между собой ромбы, и придя, в конечном счете, к одному терму:
2.5 Доказательство свойства ромба Заметьте, что (один шаг редукции) не подходит для определения свойства ромба:
Рассмотрим терм (λx.xx)(Ia), где I ≡ λx.x. За 1 шаг, этот терм редуцируется к (λx.xx)a или к (Ia)(Ia). Оба эти терма, в конечном счёте, придут к aa, но невозможно завершить ромб, используя всего 1 шаг редукции:
Проблема, конечно, в том, что (λx.xx) дублирует свой аргумент, который должен потом 2 раза редуцироваться. Заметьте, что сложные случаи включают одну редукцию, и в содержащуюся в другой. Редукции не пересекаются, например терме xMN, просто совмещаются, чтобы получить xM’N’. Свойство ромба для может быть доказано с помощью леммы “о кусках”, которая рассматривает случай (1 шаг редукции) и (возможно множество шагов):
Тогда “кусочки” могут быть сложены вместе, чтобы завершить ромб. В деталях это требует рассмотрения очень скучных случаев анализа возможных редукций от различных форм термов. 2.6 Вероятность незавершения редукции Хотя различные последовательности редукций не могут прийти к различным нормальным формам, они могут выдать совершенно разные результаты: одна может завершиться, а другая пожжет работать бесконечно! Типично, что если M имеет нормальную форму и допускает применение бесконечного числа редукций, то он содержит подтерм L, которые не имеет нормальной формы, и L может быть удалён применением редукции. Например, вспомним Ω, который редуцируется сам в себя, Ω ≡ (λx.xx)(λx.xx). Редукции достигает нормальной формы, удаляя Ω. Это соответствует обработки функции по принципу вызов по имени: его аргумент не редуцируется, а вставляется вместо в тело абстракции. При попытке нормализовать аргумент получим не завершающуюся цепочку редукций: Оценивать аргумент перед тем, как вставлять его в тело соответствует обработки функции по принципу вызова по значению. В этом примере, стратегия вызова по значению никогда бы не привела к нормальной форме. 2.7 Редукция в обычном порядке Стратегия редукции в обычном порядке состоит в том, что на каждом шагу производить β-редукцию к самому крайнему левому внешнему члену. (η-редукция может быть оставлена на потом) Самый крайний левый член означает, что нужно редуцировать L прежде, чем редуцировать N в LN. Внешний означает, что сначала надо редуцировать (λx.M)N, прежде чем M или N. Редукция в обычном порядке соответствует вычислению по принципу вызова по имени. По теореме о стандартизации, она всегда достигает нормальной формы, если она существует. Доказательство мы опустим. Однако, заметьте, что, редуцируя сначала L в LN, можно привести L в абстракцию, например λx.M. При редуцировании (λx.M)N N может исчезнуть.
2.8 Ленивые вычисления С точки зрения теории, редукция в обычном порядке является оптимальной, поскольку она всегда приходит к нормальной форме, если она существует. Для практических вычислений, это безнадежно неэффективно. Представьте, что вы закодировали натуральные числа (для этого посмотрите следующий раздел) и определили функцию вычисления квадрата . Тогда и нам бы пришлось вычислять 4 копии терма N. При вызове по значению нам бы понадобилось вычислять N заранее (только 1 раз), но, как мы убедились, редукция может не завершится. Заметьте: многобуквенные идентификаторы (как sqr) выделены жирным шрифтом или подчёркнуты, для того чтобы предотвратить путаницу с последовательностью различных переменных (как sqr). Ленивые вычисления или вызов по необходимости никогда не вычисляют аргумент более одного раза. Аргумент не вычисляется до тех пор, пока его значение необходимо для вывода ответа; даже тогда, аргумент вычисляется только до то степени, которая необходима (таким образом, позволяя бесконечные списки). Ленивые вычисления могут осуществляться представлением терма графом, нежели деревом. Каждый общий узел графа представляет подтерм, чье значение необходимо вычислять более 1 раза. Всякий раз, когда подтерм редуцируется, результат перезаписывает узел, и другие ссылки на него будут мгновенно иметь доступ на изменение. Редукция с помощью графа является не эффективной для λ-вычислений, потому что подтермы обычно содержат свободные переменные. Во время каждой β-редукции, тело абстракции должно быть скопировано. Редукция с помощью графа работает намного лучше для комбинаторов, где нет переменных вообще. Вы вернёмся к этому вопросу позже.
3 Кодирования данных в λ-вычислениях λ-вычисления являются достаточно мощное, чтобы закодировать переменные булево типа, упорядоченные пары, натуральные числа и списки – любые структуры данных, которые мы захотим использовать в функциональном программировании. Эти кодирования позволяют нам моделировать функциональное программировании в простых пределах λвычисления. Кодирование может показаться не таким натуральным, и они, конечно, они не эффективны в вычисления. В этом они похожи на кодирования и программы машины Тьюринга. В отличие от программ машин Тьюринга, кодирование само являются математическими выражениями, и возвращают вновь и вновь в теоретических науках. Многие из них используют идею, что данные могут содержать структуры контроля внутри себя. 3.1 Переменные булевого типа Кодирование булевых переменных должно определять термы true, false и if (удовлетворяющих всем M и N)
Следующее кодирование обычно опускают:
по теореме Чёрчеля-Россера, поскольку true и false являются Мы имеет разными нормальными формами. Бывает, что if вовсе не обязательно. Истинные значения равняются их собственным условным операторам:
Эти редукции верны для всех термов M и N, так или иначе, они имеют нормальные формы. Заметьте, что ; это по существу тождественная функция для L. Уравнения, описанные выше, могут рассматриваться как редукция:
Все обычные операции на значение истинности могут быть определены через условные операторы. Здесь описано отрицание, пересечение и объединение:
3.2 Упорядоченные пары Представьте, что true и false уже определены, как это было сделано выше. Функция pair, которая создаёт пары, и проекции fst и snd, которые выбирают компоненты пары, закодированы следующем образом:
Очевидно, , сворачивающий M и N вместе. Пару можно применять к любой 2-местной функции вида λxy.L, возвращающей L[M/x][N/y]; таким образом, каждая пара подвергается собственной операции развёртки. Проекции работают по принципу операции развёртки (которая, возможно, удобна более в программировании, чем для собственно проекций):
. Заметьте, что компоненты pair MN являются Аналогично, полностью независимыми; любой может быть извлечён, даже если другой не имеет нормальной формы. Упорядоченные n кортежей могут быть представлены аналогично, но вложенные пары проще в кодировании. 3.3 Натуральные числа Следующие вычисления натуральных чисел является тем самым, что разработал Чёрч. Другие кодирования иногда более предпочтительнее сегодня, но нумералы Чёрча продолжают нашу тему о размещение элементов управления внутри самих данных. Такое
кодирование элегантно, более того, оно работает в λ-вычислениях второго порядка (представленные в курсе о типах Андрю Питса). По определению:
Таким образом, для всех n ≥ 0, нумералы Чёрча n являются функциями, которые отображают f в fn. Каждый нумерал является итерационным оператором. 3.4 Арифметические действия над нумералами Чёрча Используя кодирование, сложение, умножение и возведение в степень можно определить тут же, как:
Сложение проверить не сложно:
Умножение уже немного сложнее:
Это верно для нумералов Чёрча m и n,но не для всех термов M и N. Упражнение 3 Показать, что expt возводит в степень нумералы Чёрча. 3.5 Основные операции над нумералами Чёрча Операции, которые мы определили, не являются достаточными, чтобы определить все вычисляемые функции над натуральными числами; как насчёт вычитания? Давайте начнём с простых определений: функция, вычисляющая следующее число и функция проверки нуля:
Следующая редукция истинна для всех нумералов Чёрча n:
Например:
Функция-предшественник и функция вычитания кодируются следующим образом:
Определение функции предшественника является сложным, когда каждый нумерал является итератором. Мы должны редуцировать итератор n+1 до итератора n. При известных f и x, мы должны найти некоторую g и y, такую что gn+1y вычисляет fnx. Подходящая функция g является функцией над парой, которая отображает (x,z) в (f(x),x); тогда Эта пара ведёт себя как один элемент. Приведенная выше, функция prefn f конструирует функцию g. Контролируя, что следующая редукция должны быть простой:
Упражнение 4 Покажите, что λmn.m suc n производит сложение нумералов Чёрча. 3.6 Списки Нумералы Чёрча могут быть обобщены, чтобы представлять списки. Список [x1, x2, …, xn] будет представлен как функция, которая при применении f и y даёт fx1(fx2 … fxny)…). Такие списки будут содержать структуры управления в себе. В качестве альтернативы, давайте представим список, как это делается в Lisp или ML – через операцию образования пары. Такое кодирование намного проще понять, потому что оно ближе к реальности. Список [x1, x2, …, xn] будет представлен как x1::x2:: … :: nil . Чтобы сделать операции как можно простыми, мы будем применять 2 уровня образования пар. Каждая ячейка x::y будет представлена как (false, (x,y)), где false является разделяющим полем. Тогда nil должен быть представлен как пара, чей первый элемент является true, например (true, true), но более простое определение работоспособно. Фактически, мы можем разделять вместе с разделяющей ячейкой. Вот наше кодирование списков:
Следующие свойства легко проверить; они верны для всех термов M и N:
Обратите внимание, что выполняется как исключение, тогда как все остальные выполняются с операциями над парами. Вспомните, что такие законы, как и выполняются для всех M и N, даже для термов, которые не имеют нормальных форм! Таким образом, pair и cons являются ‘ленивыми’ конструкторами – они не вычисляют свои аргументы. Как только мы представим определение рекурсии, мы сможем работать с бесконечными списками. Упражнение 5 Модифицируйте кодирование списков, чтобы получить кодирование натуральных чисел.
4. Написание рекурсивных функций в λ-вычислениях Рекурсия, очевидно, является необходимой в функциональном программировании. Вместе с нумералами Чёрча, возможно определить почти все вычислимые функции над натуральными числами. Натуралы Чёрча имеют врождённое свойство повторения. Поэтому, мы можем получить примитивную рекурсию, которая при применении функций высших порядков, определяет набор, который намного шире, чем тот набор примитивных функций, изучаемый в теории вычислений. Функция Акермана не является примитивно рекурсивной в обычном смысле, но мы можем закодировать её, используя нумералы Чёрча. Если определить то мы можем получать рекурсивные уравнения функции Акермана, то есть
Давайте проверим первое равенство:
Обратите внимание, что для 2 следующих уравнений справедливо:
Теперь проверим, что
и
Основой этих вычислений является итерационность функции 4.1 Использование неподвижной точки рекурсивными функциями
Наше кодирование функции Акермана работает, но его сложно назвать понятным. Было бы ещё хуже использования функции, при которой рекурсивные вызовы делали бы что-то более сложное, чем вычитание одного аргумента из другого – например, постоянно выполнять деление после вычитание. Обычная рекурсия может быть получена в λ-вычислениях. Таким образом, мы можем смоделировать все определения рекурсивных функция, даже для тех, для которых для некоторых аргументов (или всех) рекурсия не вычисляется. Наше кодирование является одинаковым и зависит от деталей рекурсивного определения и представления структур данных (в отличие от вышесказанной версии функции акермана, которая зависела от представления нумералов Чёрча). Секретом к такому подходу – является использование неподвижной точки – терма Y, такого, что Y F = F ( Y F ) для всех термов F. Давайте, объясним эту терминологию. Неподвижной точкой функции F является X, такой, что FX = X; здесь . Комбинатором называют любой λ-терм не содержащий свободных переменных (также называется закрытым термом). Чтобы закодировать рекурсию, F представляют телом рекурсивного определения; закон Y F = F ( Y F ) позволяет разложить F столько раз, сколько потребуется. 4.2 Примеры использования Y Закодируем функцию факториала, функцию append для списков, и бесконечный список [0, 0, 0, …] в λ-вычислениях, используя определения рекурсии:
Чтобы всё это реализовать, мы просто определим
В каждом определении, рекурсивный вызов заменяет переменную g в Y (λg. …). Давайте проверим рекурсивное выражение для zeroes; остальные проверяются аналогично:
4.3 Использования Y В общем, достаточное решение рекурсивное выражение M = PM, где P является любым λтермом, является определения Давайте рассмотрим особый случай, когда M является функцией от n аргументов. Достаточное решение уравнение Mx1…xn = PM является: тогда
Давайте, реализуем общее рекурсивное определения для M и N, с соответствующими телами P и Q:
Идея заключается в использовании неподвижной точки для функции F для пары, так что F( X, Y ) = ( PXY, QXY). Использование наше кодирование пары, определим
Использую свойство неподвижной точки,
и применяя проекции, мы получим требуемый результат
4.4. Определение комбинатора неподвижной точки Комбинатор Y был открыт Хаскелем Б. Карри. Он определяется как Давайте посчитаем, чтобы показать свойство неподвижной точки:
То есть мы выполнили 2 раза β-редукцию и после β-преобразование. Редукция невозможна! Существуют другие комбинаторы неподвижной точки, такие как комбинатор Алана Тьюринга:
В самом деле, существует редукция Вот комбинатор неподвижной точки, разработанный Клопом:
Доказательство опущено для выполнения самостоятельной проверки. Подсказка: замечайте вхождения r! Любой комбинатор неподвижной точки может быть использован, чтобы сделать рекурсивные определения при применении так называемого вызова редукции по имени. Позже, мы изменим Y, чтобы получить комбинатор неподвижной точки, который работает при применении вызова по значению. На практике компиляторы должны вычислять рекурсию непосредственно, поскольку оператор неподвижной точки является неэффективным. 4.5 Основная нормальная форма Если M = xM тогда M не имеет нормально формы. Если , где N находится в нормальной форме, тогда N = xN. Тогда xN также находится в нормальной форме, теорема Чёрча-Россера даёт нам, что Но N не может содержать себя в качестве подтерма!
По аналогичным причинам, если M = PM тогда M обычно не имеет нормальной формы, только если P является функцией константы или функции идентификатором. Поэтому что-либо определённое с помощью комбинатора неподвижной точки, например fact, не должно иметь нормальной формы. Хотя fact не имеет нормальной формы, мы всё равно можем считать с помощью него, имеет нормальную форму, а именно Мы можем использовать бесконечные объекты (включая вышеуказанные функции, а также ‘ленивые’ списки), вычисляя конечную часть от них и вычисляя за конечное время. Чтобы формализовать это, давайте определим понятие основной нормальной формы (hfn). Определение 7 Терм находится в hfn тогда и только тогда, когда он состоит из следующего вида: Примеры термов, находящихся в hfn Но не находится в hfn, потому что может быть использована так называемая основная редукция: Давайте отметим очевидные факты. Терм, находящийся в нормальной форме также находится и в основной нормальной форме. Кроме того, если тогда N должна иметь форму где Таким образом, основная нормальная форма фиксирует внешнюю структуру любой дальнейшей редукции и финальную нормальную форму (если она существует!). Поскольку аргументы не могут пересекаться друг с другом, они могут быть вычислены независимо друг от друга. Редуцируя терм M до hfn, мы можем получить конечное величину значения M.При дальнейшем вычисление hfn для мы получим следующие значение. Мы можем продолжать вычислять hfn для до любого уровня глубины и можем остановиться в любой момент. Например, определим . Это является аналогом функции zeroes, но для пар: Получим:
С помощью мы достигнем основной нормальной формы, которую мы будем продолжать редуцировать. Сначала имеем и поскольку одна и та же редукция работает, если ze является аргументом функции. Это примеры использования конечных вычислений, вовлекающих бесконечные значения. Некоторые термы не имеют основной нормальной формы. Вспомните Ω, Ω ≡ (λx.xx)(λx.xx). Терм редуцируется к hnf постоянно повторяя самую левую редукцию. С Ω мы можем сделать только что не делает её ближе к hnf. Другой терм, который не имеет hnf является λy. Ω, мы можем редуцировать только так: Можно показать, что если MN имеет hnf, то тогда её имеет и M. Поэтому, если M не имеет hnf, тогда ни один из следующих термов не будет её Терм, имеющий hnf ведёт себя как полностью определённая функция: что бы вы ни подставляли в качестве её аргументов, вычисления ничего не даст никакой информации. Не сложно увидеть, что
если M не имеет hnf, тогда не имеет его и λx.M или M[N/x], поэтому M ведёт себя как чёрная дыра. Единственный путь избавится от M является редукция, например . Это мотивирует на следующее определение. Определение 8 Терм является неопределённым тогда и только тогда, когда он может быть редуцирован к основной нормальной форме, иначе он называется неопределённым. Упражнения ниже, некоторые из которых являются сложными, более детально раскрывают эту тему. Упражнение 6 Являются ли следующие термы определёнными? (
)
Упражнение 7 Терм называется вычислимым тогда и только тогда, когда существуют переменные и термы такие что Исследуйте, являются ли термы в предыдущем упражнении вычислимыми. Упражнение 8 Покажите, что если M имеет hnf тогда M является вычислимым. Вадсворд доказал, что M является вычислимым тогда и только тогда, когда M имеет hnf, но доказательство в обратную сторону намного сложнее. 4.6 Отступление: объяснение Y Идея использования редукции в том, что мы можем просто положить, что даже не спрашивая, почему это работает. Однако оригинал Y имеет более интересную связь с развитием математической логики. Алонзо Чёрч придумал λ-вычисления чтобы формализовать теорию множеств. Бертранд Рассел продемонстрировал (намного раньше) противоречивость обычной теории множеств. Если нам позволят построить множество , тогда только Это было названо парадоксом Рассела. тогда, когда В своей теории Чёрч закодировал множества через их характеристические функции было закодировано как аппликация N(M), (эквивалентно предикатам). Отношение которая может быть истинной или ложной. Множество абстракций было закодировано, как λx.P, где P было некоторым λ-термом, выражающим свойство x. К сожалению, для Чёрча, парадокс Рассела был обнаружен в его системе! Множество Рассела кодируется, как . Предположение было противоречием, если смотреть как на логическую формулу. Фактически, RR не имеет основной нормальной формы: он так же не определён как и Ω. Карри обнаружил это противоречие. Уравнение неподвижной точки вытекает из , если мы подставим вместо not произвольный терм F. Поэтому Y часто называют комбинатором парадокса. Из-за парадокса, λ-вычисления выжили только в качестве теории уравнения. Типичные λ-вычисления не допускают никаких парадоксов и используются для формализации синтаксиса логики высших порядков.
4.7 Резюме: λ-вычисления против машин Тьюринга С помощью λ-вычислений можно закодировать такие структуры данных, как булевы переменные и списки, так что они удовлетворяют обычным законам. С помощью λвычислений можно так же выражать рекурсивные определения. Потому что кодирование является техническим, может показаться, что они не достойны, чтобы их изучать, но это не так. • Кодирование натуральных чисел через натуралы Чёрча является ценным в более продвинутых вычислениях, такие как λ-вычисления 2-го порядка • Кодирование списков с помощью упорядоченных списков моделирует способ, с помощью которого они представляются в компьютере • Как было только что сказано, определение Y формализует парадокс Рассела • Представление рекурсивных определений как неподвижную точку является обычным процессов в теории семантики Эти конструкции и понятия встречаются везде в теоретической компьютерной науке. Это не может быть сказано для любой программы машины Тьюринга.
5 λ-вычисления и теория вычислений λ-вычисления является классической моделью вычислений, так же как и машина Тьюринга и общие рекурсивные функции. Тезис Чёрча гласит, что вычислимые функции являются теми, что можно представить в λ-вычислениях. Далее мы увидим, что λвычисления имеют такую же мощность, как и все рекурсивные функции. Мы также увидим сильную неразрешимость результатов λ-термов. Следующее определение является фундаментальным. Определение 9 Если f является функцией n-го расположения в натуральных числах, тогда f является представимой в λ-вычислениях, если существуют λ-терм F, такой что для всех
Другими словами, F отражает номера аргументов в натуралы для результатов f. Используя теорему Чёрча-Россера и тот факт, что нумералы находятся в нормальной форме, мы имеем редукцию
Таким образом, мы можем посчитать значение
, нормализуя терм
5.1 Примитивные рекурсивные функции В теории вычислений, примитивные рекурсивные функции строятся из следующих базовых функций: 0 константа нуля suc функция следования функция проекции, Новые функции строятся с использованием подстановки и примитивной рекурсии. Подстановка или более общо композиция, берёт m-тый аргумент функции g и n-ый аргумент функций ; подстановка функции f так что
Примитивная рекурсия берёт n-ый аргумент функции g и (n+2)-ый аргумент функции h; в результате он даёт n+1 аргумент функции f:
5.2 Представление примитивно рекурсивных функций Функции определяются в λ-вычислениях достаточно ясно. Доказательство того, что они правильны ( с учётом наших определений о определимости в λ-вычислениях), опущено в качестве упражнения. Вот базовые функции: • Для 0 использовать , то есть . . • Для suc использовать suc, то есть •
Для
использовать
.
Чтобы применить подстановки, предполагается, что m-ый аргумент функции g и n-ый аргумент функций определены в λ-вычислениях как соответственно. Тогда их функция композиции f будет определена в λ-вычислениях как Чтобы применить примитивную рекурсию, предполагается, что n-ый аргумент функции g и (n+2)-ый аргумент функции h определены в λ-вычислениях как G и H соответственно. Функция примитивной рекурсии f определена в λ-вычислениях как
5.3 Основные рекурсивные функции Начиная с примитивно рекурсивных функций, так называемые основные рекурсивные функции получаются при добавлении оператора минимизации или инверсии функции. Для данного n-го аргумента функции g вырабатывается n-го аргумента функции f такой что минимальный y такой что и не определён, если такого y не существует. Таким образом, минимизация может давать частичные функции. Это ведёт к определённым трудностям. • Понятие неопределённости довольно сложно в λ-вычислениях. Очень скучно показывается, что кодирование в λ-вычислениях минимизации выдаёт неопределённые термы тогда и только тогда, когда соответствующая функция не может завершить вычисления. • Композиция в λ-вычислениях является нестрогой. Например, представьте частичные функции r и s, такие что r(x) = 0 и s(x) неопределенно для всех x. Мы и . Теперь, можем определить это в λ-вычислениях, как . Определение строгой r(s(0)) должно быть неопределенно, но композиции R и S содержит подводный камень.
всех
Давайте ограничим наше внимание на полностью рекурсивных функциях. Если для существуют некоторый y, такой что , тогда инверсная
функция от g является полностью рекурсивной. Предположим, что G является термом, который в λ-вычислениях определяется, как g. Тогда обратная функция будет определяться как
Это выражение создаёт рекурсивную функцию, которая проверяет, что равняется для увеличивающегося значения у. Чтобы начать поиск, функцию применяют к . Тест на эквивалентность для натуральных чисел может быть определён по-разному, например если . Отношение эквивалентности Это работает, потому что между произвольными λ-термами ( не только для нумералов Чёрча) является неразрешимой задачей и не может быть определено в λ-вычислениях. Упражнение 9 Найдите ещё одно определение теста на эквивалентность для натуральных чисел. 5.4 Функции в λ-вычислениях являются рекурсивными Мы только что показали, что все полностью рекурсивные функции определяются в λ-вычислениях. Доказательство обратное не является очень сложным, и мы опишем его в общих чертах. Основой является присвоить уникальный (и рекурсивно вычислимый) номер Годеля #M для каждого λ-терма M. Во-первых, представьте, что множество переменных имеет форму так что переменные пронумерованы. Определение #M является достаточно произвольным; например мы можем положить
Чтобы показать, что все функции, определяемые в λ-вычислениях являются рекурсивными, предположите, что нам дан λ-терм F; мы должны найти рекурсивную функцию f, такую что тогда и только тогда, когда . Мы можем это сделать в стандартной манере, написав интерпретатор для λ-вычислений, используя язык полностью рекурсивных функций, работая над числами Годеля для λтермов. Это вопрос о том, как программировать. Результирующая программа довольно длинная, но её намного проще понять, чем такую же, написанную с помощью машины Тьюринга, а именно используя универсальную машину Тьюринга. Упражнение 10 (композиция частичных функций) Покажите, что для всех нумералов Чёрча определенный как
. Используйте тот факт, что терм H,
определяет композицию функций f и g в λ-вычислениях для данных термов F и G. Что можно сказать о HM если GM не определено? Подсказка: вспомните определение вычислимости из упражнения 7.
5.5. Теорема о 2-ой неподвижной точке Мы формализуем λ-вычисления в пределах самой теории, чтобы доказать некоторые неразрешимые результаты. Нумерация Годеля легко кодируется арифметическими операциями. На практике, существуют вычислимые функции Ap и Num, определённые над натуральными числами, такие что
Это можно определить в λ-вычислениях, используя термы AP и NUM, которые действуют над нумералами Чёрча. Давайте запишем для , что является нумералом Чёрча для номера Годеля M. Он сам по себе является λ-термом. Используя эту нотацию, AP и NUM будут удовлетворять (где Подставляя
вместо
- любой натурал Чёрча)
в последнем уравнении, получим ключевое свойство:
Теорема 10 (2-ая неподвижная точка) Если F является любым λ-термом, тогда существует терм X, такой что Доказательство Давайте, определим следующие определения:
Тогда, используя β-редукцию и законы для AP и NUM, получим
Таким образом Упражнение 11 Откуда мы знаем, что шаги в доказательстве на самом деле являются редукциями, нежели просто равенствами? Что было в 1-ой теореме о неподвижной точке? Если F является любым λ-термом, тогда существует терм X, такой что FX = X. Доказательство: положим X = Y F. Обратите внимание, что сходство между Y F и X построено выше. Мы используем комбинатор неподвижной точки, чтобы определить рекурсивные функции в λ-вычислениях; мы будем использовать теорему о второй неподвижной точки, чтобы доказать неразрешимость проблемы остановки.
5.6 Неразрешимость результатов в λ-вычислениях Мы можем показать, что проблема остановки, так как она выражена в λ-вычислениях неразрешима. Теорема 11 Не существует λ-терма, который останавливался вычисляться так, что
Доказательство Допустим обратное, а именно что остановка существует. Положим Из 2-ой теоремы о неподвижной точки следует, что существует X, такой что Возможны 2 случая; и оба ведут к противоречию: • Если X имеет нормальную форму, тогда терма нормальной формы не существует •
Если X не имеет нормальную форму, тогда
, но для данного , а этот терм имеет
нормальную форму Доказательство является типичным доводом. Теорема достаточна сильна: хотя может делать что угодно с кодом M, анализируя структуру терма, он не может выяснить имеет ли M нормальную форму. Вспомним тезис Чёрча – вычислимые функции являются теми, которые представимы в λ-вычислениях – теорема утверждает, что проблема остановки для нормализации λ-терма является вычислительно неразрешимой. Более сильные результаты доказуемы. Скотт Дана показал, что • Если любое нетривиальное множество λ-термов ( это значит, что не является ни пустым, ни множеством всех λ-термов) и • Если замкнуто относительно эквивалентности (это значит, что если и , то тогда проверка на принадлежность к неразрешима. Проблема остановки рассматривается как особый случай, полагая Более детально это описано в Барендрегта[1, стр.143]
6 ISWIM: λ-вычисления в качестве языка программирования Питер Ландин был первым учёным, который обратил внимание на λ-вычисления и приспособил их для языков программирования. Он наблюдал за правилами языка Algol 60 и правилами вызова вычислений по значению как аналогию в λ-вычислениях. В его работе [9] он выделил скелет языка программирования, основанного на λ-вычислениях. Название говорило, что уже 700 языков имеют такую структуру; в принципе они все могли бы разделять одинаковый скелет λ-вычислений, отличный только в типах представления данных и операциях. Язык Ландина, ISWIN (сокращение от “если ты видишь, что я имею в виду”) доминировал в ранней литературе по функциональному программированию, и был моделью языка ML.
LISP так же взял своё начало от λ-вычислений и возник несколькими годами раньше ISWIM. Но LISP имел несколько фатальных ошибок: динамическое масштабировании переменных, императивная ориентация и не было в наличии функций высших порядков. Хотя ISWIM позволял императивные свойства, LISP по существу является императивным языком, поскольку все переменные могут быть изменены. ISWIM был разработан, чтобы взаимодействовать с расширенными специфичными данными и операциями. Он состоял из λ-вычислений плюс некоторые дополнительные конструкции, и мог быть переведён обратно в чистые λ-вычисления. Ландин назвал дополнительные конструкции синтетическим сахаром, потому что они делали λвычисления более приятными (вкусными). 6.1 Обзор ISWIM ISWIN начинается с λ-вычислений: переменная абстракция аппликация Он также позволяет локальные определения: простое определение определение функции определение рекурсии Локальные определения может быть сделаны постфиксной записью:
Значения локальных определений должно быть очевидным. Они могут быть переведены в чистые λ-вычисления:
Программисты не предполагали закодировать данные, используя числа Чёрча и что-то в это роде. ISWIN предоставляет примитивные структуры данных: целые числа, булевы переменные и упорядоченные пары. Нет типизированной системы, списки могут быть построены с помощью повторение пар, так же как в LISP. Основные константы таковы целые числа арифметические операторы операторы отношений булевы переменные операторы над булевыми переменными условие 6.2 Вызов по значению в ISWIM Правило вызова по значению, нежели вызов по имени, обычно более приемлемо. Это было (и сейчас так) легче осуществить; мы скоро увидим, как это делается, используя машины SECD. Вызов по значению является более неопределённым при императивных операциях.
Вызов по значению обычно более интуитивен и предсказуем. Классическая математика опирается на определённые функции; выражение является неопределённым, только если все его части являются определёнными. При вызове по имени мы можем определить функцию f таким образом, что для всех x, даже для Обычная математика не может справиться с такими функциями; чтобы формализовать их в строгом базисе требуются сложные теоремы. При вызове по значению, оператор if-then-else должен быть рассмотрен как специальная форма выражения. Обращение if в функцию даёт функции fact работать бесконечно: Аргументы для if всегда вычислимы, включая рекурсивные вызовы; когда n = 0 вычисляется fact(-1). Поэтому, мы берём условное выражение как примитив, который возвращает по определённому правилу не вычисленные M или N:
Наше правило вызова по значению никогда не редуцирует что-либо окружённые оператором λ. Поэтому мы можем переводить условное выражение в аппликацию функции if: Выбирая некоторую переменную u, не являющейся свободной в M или N, заключим эти выражения в λ барьер для дальнейшего вычисления; наконец выбранный применяется к 0. 6.3 Пары, сопоставление с образом и мутирующая рекурсия ISWIM включает упорядоченные пары: конструктор пар проекции пар Для сопоставление с образом, аббревиатура где
и
будет обозначать
будут самими образцами. Таким образом, мы можем написать берём отдельно значение M определяем f над парами
Трансляция итераций выглядит как Мы можем представить n-картеж, написав
для склеенных пар
Определение мутирующих рекурсивных функций:
может быть переведено в выражение, содержащие распознавание образов:
Мы можем легко управлять в общем случае k мутирующими рекурсивными функциями, с любым числом аргументов для каждой. Обратите внимание на силу синтетического сахара! 6.4 Из ISWIM в ML Практически все опции языков программирования, включая выражения go to и указатели, могут быть формально описаны с помощью λ-вычислений, используя технику детонационной семантики. ISWIM намного проще этого; он программируется прямо в λвычислениях. Чтобы позволить императивное программирование, мы можем даже определить последовательное выполнение, декларацию M, N как аббревиатуру , правило вызова по значению, которое будет вычислять M перед N. Однако императивные операции должны быть приняты как основополагающие; они не могут быть переведены в λ-вычисления. ISWIM даёт все основные характеристики языка программирования – масштабирование переменной, определение функций и локальные определения. (определение let особенно удобно; многие языки до сих пор заставляют писать эти назначения для этой цели!) Чтобы получить реальные язык программирования, нужно многое добавить, но полученные языки будут иметь общую структуру. ISWIM был намного впереди своего времени и никогда не получил общего признания. Его влияние на ML очевидно. Стандартный ML изменил синтаксис определений – но большинство синтаксиса до сих пор определяется передом. Французский родственник ML, названный CAML, оставляет большую часть традиционного синтаксиса ISWIM[4]. 6.5 SECD машина Ландин придумал SECD машину, интерпретатор λ-вычислений, для того чтобы выполнять программы на ISWIM[3,5,8]. Это вариант выполнения машиной инструкций скомпилированных из λ-термов. С помощью некоторой оптимизации, это может быть использовано, чтобы осуществить реальный функциональный язык, такой как ML. SECD машина делает строгие вычисления – только вызов по значению. Версия ленивых вычислений намного медленнее, чем графическая редукция комбинаторов, которую мы рассмотрим позже. Он предполагает, что значение является полностью вычислимым λ-термом, а именно термом в нормальной форме. Это доказательство слабого понятия значения в функциональном программировании по 2 причинам: 1. Функции сами по себе должны быть значениями, но многие функции не имеют нормальных форм. Рекурсивные функции, кодируемые как Y F, удовлетворяют …. Хотя они не имеют нормальных форм, они могут хорошо вырабатывать нормальные формы, как результат, когда они применяются к аргументам. 2. Вычисляя тело λ-абстракции, а именно M в λx.M, мы редко заинтересованы во внутренней структуре функции. Только, когда она применяется к некоторому аргументу N, мы требуем результат и вычисление M[N/x]. Мы, очевидно, не можем закодировать что-то вроде для true и для , поскольку наши правила вычисления не редуцируют тела функций. Мы должны брать целые числа, булевы переменные, пары, и т.д. как примитивные константы. Обычные должны быть тоже примитивными. функции 6.6 Окружение и клоз Рассмотрим последовательность редукций
с помощью подстановки x. β-редукция устраняет свободное вхождение x в Подстановка достаточна медленная, чтобы быть эффективной; вместо этого, машина SECD запоминает x = 3 в своём окружении. С помощью каррированных функций является законным значением. Машина SECD представляет это в виде клоза, который пакует λ-абстракцию с её текущим окружением:
связанная переменная
тело функции
окружение
Когда машина SECD применяет это значение функции к аргументы 5, то он восстанавливает окружение x = 3, прибавляет связанную y = 5, и вычисляет x + y в этом преобразованном окружении. Клоз называется так, потому что он закрывает тело функции для своих свободных переменных. Эта операция является трудоёмкой; большинство языков программирования запрещают использовать функции в качестве значений. До недавнего времени, большинство версий языка LISP позволяло свободной переменной функции принимать любые значения, которые она могла иметь в окружении (сравните это с определением функции!); с этим подходом, вычисление
будет возвращать 18, используя 17 как значение переменной x в функции g! Это называется динамическим связыванием, в отличие от обычного статического связывания. Динамическое связывание сбивает с толку, потому что граница значений x в f(x) может выходить далеко за пределы тела f – граница включает в себе все возможные вычислимые значения из f (включая g в данном случае). Обычный LISP, в данный момент доминирующий, исправил этот недостаток путём включение статического связывания как стандарт. Но так же позволяет динамическое связывание. 6.7 Состояние машины SECD Машина SECD имеет 4 состояния: S, E, C, D: 1. Стек(S) содержит список переменных, обычно операторы или аргументы функций; он так же возвращает результат вызова функции 2. Окружение(E) имеет форму , которая означает, что переменные имеют значения соответственно. 3. Управление(C) содержит список команд. Для объяснения работы машины SECD, команда является λ-термом или словом app откомпилированная машина SECD имеет много команд. 4. Остаток(D) состоит из ничего или другого набора состояний машины S, E, C, D. Обычно это выглядит следующем образом По существу это есть список троек функция вызова стека.
и служит как
6.8 Переходы состояний Давайте запишем состояния машины SECD в виде таблице: Стек(S) Окружение(E) Управление(C) Остаток(D) Чтобы вычислить λ-терм M, машина начинает вычисление с инициализации состояния, где M является управлением:
Если управление не является пустым, то первая команда инициализирует переход состояния. Есть разные случаи для констант, переменных, абстракций, аппликаций и команды app. Константа выталкивается в стек:
Значение переменной берётся из окружения и выталкивается в стек. Если переменная x и окружение E тогда константа x = a и a выталкивается:
λ-абстракция переходит в клоз, после чего выталкивается в стек. Клоз содержит текущее значение окружения:
Аппликация функции заменяется на код, чтобы вычислить аргумент и саму функцию, после этого ставится указание app:
Указание app даёт команды создать функцию в голове списка, используя второй аргумент в стеки как аргумент функции. Примитивные функции, такие как + или , выдают результат тут же:
Клоз вызывается с помощью создания нового состояния, чтобы вычислить M в окружении E’, расширенного с помощью связывания аргументов. Старое состояние сохраняется в значение остатка:
Функция завершает вычисления в состоянии, когда управление пусто, но остаток нет. Чтобы возвратится из функции, машина восстанавливает состояние (S, E, C, D) из остатка и выталкивает a в стек. Это следует после перехода состояний:
Результат вычислений, скажем a, получается из финального состояния, где управление и остаток являются пустыми, и a является единственным значением в стеке:
6.9 Пример вычисления Чтобы продемонстрировать, как работает машина SECD, давайте вычислим выражение , где есть и является встроенной функцией вычисления является просто нумералом Чёрча ). Мы квадрата. (Обратите внимание, что начнём с инициализации состояния:
Машина завершает вычисления в финальном состоянии, давая результат 81. 6.10 Откомпилированная машина SECD Пришлось совершить 17 шагов, чтобы вычислить ! Намного быстрее выполнение происходит, если откомпилировать сначала λ-терм. Будем писать как список команд, созданных чтобы откомпилировать М; существуют случаи для каждого из 4 разновидностей λ-термов. Константы компилируются с помощью команды const, которая в последствии (во время выполнения кода) вытолкнет константу в стек: Переменные компилируются с помощью команды переменной из окружения в стек:
, которая вытолкнет значение
Абстракция компилируется с помощью команды , которая вытолкнет клоз в стек. Клоз будет включать в себя текущее окружение и будет содержать M как список команд из компиляции: Аппликация компилируется с помощью команды сделано в считанные секунды:
. В SECD машине это будет
Мы можем увеличить количество инструкций, например условные операторы. Пусть будет заменять на или в зависимости от значения в вершине стека – true или false: Построение функции 2-х переменных таких как или может быть сделано несколькими способами. Эти функции могут быть созданы, чтобы работать с упорядоченными парами, построенные с помощью команды pair. Более эффективно является представление арифметические инструкции, таких как и , которые будет откомпилировано извлекают оба своих операнда из стека. Тогда в и будет сгенерировано 2 списка команд:
Множество дальнейшей оптимизации может быть сделано, создающая модель выполнения вычислений достаточно удобной для определённого железа. Имена переменных могут быть удалены из окружения и связанные переменные ссылаются на глубину нежели на имя. Специальные инструкции enter и exit могут эффективно управлять функциями, которые вызываются незамедлительно (например, те которые объявляют ), не создавая при этом клоз: Функции хвостовой рекурсии (иногда называется повторяющейся) могут быть скомпилированы с помощью команды , которая сделает следующие переход:
после к C!
никогда не сохранится в остатке, и функция Бесполезное состояние никогда не выполнится – поэтому машина просто перескакивает прямиком
6.11 Рекурсивные функции Обычный комбинатор неподвижной точки Y не работает на SECD машине; он всегда зацикливается. Модифицированный комбинатор неподвижной точки, содержащий ещё 1 λ оператор чтобы задержать вычисления, используется: Но он очень медленный! Рекурсивные функции являются лучшими инструментами для того, чтобы создавать клозы с указателями самих на себя. Предположим, что является определением рекурсивной функции. Это значение f отображается в . Машина SECD должна интерпретировать специальным образом, применяя клоз к пустому значению, . Если текущее окружение является E, тогда вырабатывается клоз После машина модифицирует клоз, заменяя клоз:
на указатель, указывающий на этот самый
Когда применяется клоз, рекурсивный вызов f в M будет возвращать тот же самый клоз. Такое циклическое окружение делает рекурсию неэффективной. Данная техника называется “попытка развязать узел” и работает только для определённых функций. Она не работает для рекурсивных определений структур данных, таких как бесконечные списки , определённые как . Поэтому строгие языки, такие как ML, позволяют только функциям быть рекурсивным.
7 Ленивое вычисление с помощью комбинаторов Машина SECD использует вызов по значению. Она может быть изменена, чтобы для вызова по имени (ленивые вычисления). Когда вызывается функция, её аргументы
хранятся не вычисленными в клозе, содержащий текущие окружение. Таким образом, под , где u ту встречается в N. Этот клоз вызовом MN подразумевается что-то вроде называется приостановкой. Когда вызывается строгая встроенная функция, такая как +, её аргумент вычисляется обычным образом. Это существенно, что аргумент не будет вычисляться более 1 раза, в независимости от того, сколько раз он встречается в теле функции:
В этом выражении было вычислено значение путём повторения дублирования аргумента функции , потери будут катастрофично сказываться. Поэтому, ленивая машина SECD обновляет окружение значения аргумента после того, как она вычислила его в первый раз. Но цена за создание приостановки согласно Давиду Тёрнеру, делает эту машину в 10 раз медленнее, что строгая машина SECD , а компиляция дает небольшое улучшение. 7.1 Редукция графом в λ-вычислениях Другая идея состоит в том, чтобы непосредственно работать с λ-термами, используя коллективное использование и обновление, чтобы гарантировать, что ни один аргумент не вычислялся более 1 раза. Например, вычисление может быть представлено в виде редукции графом
Сложность здесь в том, что λ-абстракции могут сами быть коллективно использованными. Мы не можем изменять тело абстракции, заменяя связанные переменные фактическими аргументами. Вместо этого, мы должны копировать тело – включая части, в которые не содержать связанных переменных – когда выполняем подстановку. И ленивая машина SECD и редукция графом λ-термов страдает из-за связанных переменных. Комбинаторы имеют одинаковую выражающую силу как λ-вычисления, но не содержит проблем связанных переменных. Редукция графов с помощью комбинаторов не требует копирования. Давид Тёрнер нашёл эффективный метод перевода λ-термов в комбинаторы, чтобы вычислять с помощью редукцией графов[10]. Продолжения этого метода были широко признаны для реализации ленивых языков программирования. 7.2 Введение в комбинаторы В самой простой версии существует всего 2 комбинатора – K и S. Комбинатору по существу являются константами. Существует возможность определить K и S в λвычислениях, но комбинаторная логика существует как отдельная наука. Термы комбинаторной логики, будем обозначать P,Q,R,…, строятся из K и S, используя аппликацию. Они могут содержать свободные переменные, но не связанные . Хотя переменные. Типичный комбинаторный терм комбинаторная логика не даёт прочить содержимое, она достаточно мошна, чтобы закодировать все вычислимые функции! Комбинаторы должны выполнять следующую редукцию:
Таким образом, комбинаторы могут быть определены в λ-вычислениях, как
Но обратите внимание, что S K не редуцирует – поскольку S требует 3 аргумента – в то время как соответствующий λ-терм нет. Поэтому, редукцию комбинатором принято называть слабой редукцией (поэтому буква w в означает слабую рекурсию от английского weak). Вот пример слабой редукции: Поэтому для всех комбинаторных термов P; давайте определим комбинатор идентификации как . Многие принципы λ-вычислений действуют и с комбинаторами. Комбинаторный терм P находится в нормальной форме, если к нему нельзя применить слабую рекурсию. Для комбинаторов существует своя версия теоремы Чёрча-Россера: если P = Q (для любого числа редукций, прямых или обратных) тогда существует терм Z, такой что и . 7.3 Абстракция в комбинаторах Любой λ-терм может быть переведён неточно в эквивалентный комбинаторный терм (понятие “неточно” объясняется далее). Основополагающим является перевод одного комбинаторного терма P в другой комбинаторный терм, записывается, как поскольку он ведёт себя как λ-абстракция. Определение 12 Операция , где x является переменной, определяется рекурсивно как:
Наконец,
является аббревиатурой
.
Обратите внимание, что не является частью синтаксиса комбинаторной логики, но обозначает терм построенные согласно определению, данному выше. Вот пример комбинаторной абстракции:
Каждый может удвоить число аппликаций в терме; в общем случае, увеличение является экспоненциальной. Тёрнер обнаружил более благоприятный метод абстракции, показанный в следующей секции. Для начала давайте покажем, что комбинаторная абстракция ведёт себя так же как λ-абстракция. Пусть FV будет определено для комбинаторных термов так же, как это было сделано в определении 3
Теорема 13 Для любого комбинаторного терма P мы имеем
Доказательство Мы докажем оба свойства по отдельности с помощью индукции по P. Существуют 3 случая. Если P является переменной x, тогда
. Очевидно
Если З является термом, не содержащим x, тогда
и
Если
и x является свободной переменной в Q и R, тогда . Это является индуктивным шагом, и мы можем предположить в качестве предположения индукции, что теорема работает для P и Q:
Теперь рассмотрим множество свободных переменных:
Наконец рассмотрим аппликацию:
, мы можем получить аналоги β-редукции для Используя комбинаторной логики. Мы так же получаем сильную аналогию α-преобразований – изменения переменных в абстракции является несущественным, давая идентичные термы. Теорема 14 Для всех комбинаторных термов P и Q
Доказательство Оба утверждения являются структурными введениями; первое утверждение можно так же получить из предыдущей теоремы, используя общую теорему о подстановке[1].
7.4 Отношения между λ-термами и комбинаторами переводит λ-терм в комбинаторный терм. Он просто рекурсивно Отображение применяет ко всем абстракциям в λ-терме; обратите внимание, что самые внутренние абстракции выполняются в первую очередь! Обратное отображение переводит комбинаторный терм в λ-терм. Определение 15 Отображения
и
определяются рекурсивно следующим образом:
Разные версии комбинаторной абстракции вырабатывают разные версии ; тот, который представлен, даёт экспоненциальный рост в размере терма, но эта версия как и как . является простой для доказательств. Давайте сократим Очень легко проверить, что и не добавляют и не удаляют свободные переменные: Понятие равенства намного более проблематично. Отображение даёт значительное соответствие между λ-вычислениями и комбинаторной логикой, мы принимает принцип экстенсиональности. Это утверждает, что 2 функции являются эквивалентными, если они возвращают одинаковые результаты для одинаковых аргументов. В комбинаторной логике, экстенсиональность имеет форму нового правила для подтверждения равенства:
В λ-вычислениях, экстенсиональность может быть выражена в виде простого правила или в η-редукции:
При экстенсиональности, отображение сохраняет равенство[1]:
Нормальные формы и редукции не сохраняют равенство. Например, является нормальной формой в комбинаторной логике; слабая редукция не может быть применена. Но соответствующий λ-терм не находится в нормальной форме: Существуют комбинаторные термы в нормальной форме, для которых соответствующие λ-термы не имеют нормальных форм! Даже когда оба терма редуцируются по похожей редукционной последовательности, редукция в комбинаторной логике имеет намного
большую глубину, чем в λ-вычислениях; представьте сколько шагов требуется, чтобы сделать β-редукцию в комбинаторной логике. Нормальные формы являются результатами функциональных программ; конечно, они должны быть сохранены. Редукция является процессом генерации результатов. Обычно мы не должны беспокоиться об этом, но ленивые вычисления могут работать с бесконечными результатами (бесконечными списками, …) и поэтому не могут быть вычислены полностью. Таким образом, глубина и норма редукции очень важны. Несмотря на неполное соответствие между λ-термами и комбинаторами, компиляторы, основанные на комбинаторной логике, работают. Возможно, вещи, которые не сохраняются, являются не существенными для результатов вычисления. Более глубокие исследования должны быть сделаны в области поведении функциональных программ.
8 Методы компилирования, использующие комбинаторы Комбинаторная абстракция даёт нам теоретический базис для устранения переменных из λ-термов, и позволить делать эффективную редукцию графом. Но сначала, нам нужно отображение из λ-термов в комбинаторы, которые генерируют более компактные результаты. Вспомните, что приводит к экспоненциальному росту размеров: Улучшенная версия комбинаторной абстракции основывается на 2 новых комбинаторах, B и C, чтобы обрабатывать особые случае для S:
даёт функциональную композицию P и Q. Давайте Обратите внимание, что представим новое отображение абстракции в честь Давида Тёрнера, его изобретателя:
Хотя более сложен, чем , он генерирует более лучшую комбинацию комбинаторов. Третий случай, для P x, использует свойство экстенсиональности; заметьте его сходства с η-редукцией. Два следующих случая абстрагирует в зависимости от того, являются ли переменные абстракции свободными в P или Q или нет. Давайте рассмотрим пример:
Размер сгенерировано кода был уменьшен в 4 раза! Вот ещё один пример из билета № 6 экзаменационной сессии 1993 года. Давайте переведём λ-кодирования в последовательность упорядоченных пар:
К сожалению, до сих пор создаёт квадратичное увеличение размера; дополнительные примитивные комбинаторы должны быть представлены (смотри Филд и Гаррисон [5,стр.286]). Более того, все константы функционального языка – числа, арифметические операторы, … – должны быть использованы как примитивные комбинаторы. Введение всё больше и больше примитивных операторов делает код всё меньше и быстрее. Это ведёт к методам супер комбинаторов, где множество примитивных комбинаторов извлекается из программы автоматически (само по себе). Упражнение 12 Покажите, что
, используя экстенсиональность.
Упражнение 13 Подтвердите, что ведёт себя так же как λ-терм или более аргументам. Упражнение 14 Что выработает
, когда применяется к двум
, если бы не было бы третьего случая в определении
?
8.1 Комбинаторные термы и графы Рассмотрим программу на ISWIM Давайте переведём её в комбинаторы:
Это закрытый терм – он не содержит свободных переменных (и естественно он не содержит связанных переменных). Поэтому он может быть вычислен при помощи редуцирования к нормальной форме. Редукция графом работает над графической структурой комбинаторного терма. Оно напоминает бинарное дерево с разветвлением при каждой аппликации. Графическая структура для представлена здесь:
Повторяющиеся аргументы могут быть коллективно использованы, гарантируя, что они не будут вычисляться более 1 раза.
8.2 Примитивное преобразование графов Редукция графом имеет дело с термами, которые не содержат переменных. Каждый терм и его подтермы обозначают постоянные значения. Поэтому мы можем изменять граф деструктивно – операнды никогда не копируются. Граф заменятся своей нормальной формой. Примитивные комбинаторы сокращаются как показано на рисунке 1. Коллективное использование в редукции для S является очень важным, так как при этом нет необходимости копировать (и вычислять) R. Нам так же нужны правила редукции графа для встроенных функций, такие как . Так как является строгой функций, граф для может быть сокращён только после того, как P и Q будут сокращены до цифровых констант m и n. Тогда будет заменено на константу, значение которого . Редукция графом продолжается следовать к следующей цели, спускаясь вниз по графу к самой левой ветки (разветвлению). Если самый левый элемент является одним из комбинаторов или с необходимым числом операндов, тогда он применяется с соответствующей трансформацией. Если самый левый символ является прямым комбинатором, таким как , тогда он рекурсивно просматривает операнды, пытаясь сократить их до чисел. Рисунок 2 показывает последовательность редукций графом для программы на ISWIM
Соответствующая редукция терма:
Очень хорошо видно при редукции графом, но не при редукции терма, что 2 копии от 5 был являются коллективно использованными. Если, например, вместо 5 аргументом бы большой комбинаторный терм P, компилируемый программой, тогда P вычислялся бы 1 раз. Редукция графом также избавляется от термов использованием правила ; здесьQ никогда не вычисляется.
Рисунок 1: Редукция графом комбинаторов
Рисунок 2: Последовательность редукций графом
8.3 Булевы переменные и пары λ-вычисления может кодировать упорядоченные пары, нумералы Чёрча и так же работать с комбинаторами, но их невозможно использовать для компилирования функционального языка. Новые комбинаторы и новые редукции введены вместо этого. В сочетании с ленивыми вычислениями, правило if-then-else может быть использовано, как функция, имеющая 2 редукции:
Эти редукции избавляются от P или Q если они не требуются; нет необходимости в вырабатывании методов для отложения вычисления. Первая редукция работает так, как показано на рисунке.
Образование пар также является ленивым вычислением, как это есть в λвычислениях; мы введём редукции
Соответствующая редукция графом должна быть очевидной:
8.4 Рекурсия: циклические графы Перевод Y в комбинаторную форму будет работать, вырабатывая многоуровневую редукцию
Это весьма не эффективно; Y должен повторять свою работу при каждом рекурсивном вызове! Вместо этого, возьмём Y как примитивный комбинатор, удовлетворяющий , и введём правило для редукции графа, который заменяет Y на цикл:
Поскольку P не копируется, редукции, которые возникают при выполнении постоянных упрощений, не повторяются, когда функция вызывается рекурсивно. Чтобы продемонстрировать это, рассмотрим программу на ISWIM Результатом должен быть бесконечный список комбинаторы, начиная с
. Мы транслируем from в
и получаем (проверьте это)
Рисунки 3 и 4 показывают редукцию графом. Циклический узел, названный , тут же появляется. Его извилистая трансформация генерирует рекурсивное расположение функции глубже в графе. Серия редукций предполагает, что окружение требует вычисления результата; в ленивых вычислениях, ничего не происходит (не вычисляется) до тех пор, пока это не заставляет случиться. Редукция графом оставит терм не вычисленным до тех пор, пока что-то не потребует его значения; результатом является в действительности . Редукция графом работает в какой-то степени как макрорасширение. Не рекурсивные функции вызываются для того, чтобы вычисляться только однажды и это действует для всех только тогда, когда необходимо их вычисление; таким образом, программистам позволено определять множество простых функций, чтобы способствовать читаемости программ. Аналогично, константное выражение вычисляется
только однажды и это действует для всех только тогда, когда необходимо их вычисление. Хотя такое поведение устраняет невыгодное перевычисление, он может послужить причиной того, что граф будет расти и расти, уничтожая всю память – это называется утечка пространства. Показанная редукция графом показывает, как это может произойти. Упражнение 15 Переведите Y в комбинаторы и сделайте несколько шагов для редукции Y P .
Рисунок 3: Редукция, использующая рекурсию
Рисунок 4: Редукция, использующая рекурсию (продолжение)
Литература