Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:

КН Лекции 1-9

.pdf
Скачиваний:
16
Добавлен:
23.02.2015
Размер:
2.64 Mб
Скачать

 

E

→*

→*

 

 

 

 

 

 

 

 

E2

E1

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

→*

 

 

→*

 

N

 

 

 

 

 

 

Мы используем символ →* для обозначения произвольного числа, п !3s 0, редукций, т. е. →* является рефлексивным транзитивным замыканием отношения →. (Рефлексивность означает, что Е можно преобразовать само в себя, не делая ничего, т. е. за n = 0 шагов.) Формально ромбическое свойство можно записать так:

E →* E1 и E →* E2 => N : E1 →* N и E2 →* N

Отношение редукции называется редукцией Черча-Россера, если отношение обладает ромбическим свойством.

§ 7. Проблема конфликта имен

Вернемся к рассмотрению α-преобразования. Бывают случаи, когда без переименования переменных, т.е. без α-преобразования не обойтись.

Пример:

λy.(λx.λy.+ x y) y

Здесь также имеются два лямбда-выражения, имеющих одну и ту же переменную - y. Соответственно, во внешнем лямбда-выражении одно вхождение переменной y является свободным, а другое - связанным. В этом выражении имеется единственный редекс, представляющий собой тело функции, определенной внешним лямбдавыражением. Попытка применить β-редукцию без предварительного проведения α-преобразования приведет к ошибке: мы получим

выражение λy.(λy.+ y y), в котором свободное вхождение переменной y попало в позицию, где переменная y связана.

Убедимся, что полученное выражение не эквивалентно исходному, применив исходную и результирующую функцию к одним и тем же аргументам, например, к аргументам 1 и 2. Для исходного выражения,

применяя β-редукции в нормальном порядке, последовательно получим следующие выражения:

((λy.(λx.λy.+ x y) y) 1 2)

 

 

(((λx.λy.+ x y) 1) 2)

((λy.+ 1 y) 2)

(+ 1 2)

→ 3.

Для выражения, полученного в результате неправильного

применения редукции, получим:

 

 

((λy.(λy.+ y y)) 1 2)

((λy.+ y y) 2)

(+ 2 2)

4.

Можно высказать и более общее утверждение.

Утверждение β-редукция может привести к ошибке тогда, когда выражение, имеющее в своем составе свободную переменную, подставляется в качестве аргумента в выражение, имеющее в своем составе связанную переменную с тем же именем.

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

Если имеется некоторое лямбда-выражение, содержащее внутри себя редексы, то при применении АПР редукции в теле лямбдавыражения всегда будут производиться до того, как это лямбдавыражение будет применено к аргументу. Однако при применении НПР необходимость в выполнении редукций внутри лямбда-выражения может возникнуть только тогда, когда это лямбда-выражение не применяется уже ни к каким аргументам, в противном случае, сначала будет произведена подстановка аргумента, а затем уже будет производиться редукция полученного выражения. Это означает, что мы можем избежать необходимости переименования переменных в выражении, если будем применять НПР, оставляя редексы внутри лямбда-выражений без преобразования. Следуя этому правилу, мы в результате последовательного применения β- и δ-редукций не получим нормальной формы выражения, однако, получим довольно близкий ее эквивалент - так называемую

слабую заголовочную нормальную форму (сокращенно - СЗНФ).

Ранее мы определяли нормальную форму как выражение, не содержащее внутри себя редексов. Если рассмотреть все возможные

типы выражений, то мы увидим, что выражение, находящееся в нормальной форме может иметь один из следующих видов:

константа c (в том числе - одна из встроенных функций);

переменная x;

лямбда-выражение λx.e, где e - выражение, находящееся в нормальной форме;

f e1 e2... ek, где f - встроенная функция с n аргументами, e1, e2,... ek

- выражения в нормальной форме, причем k < n.

Последний вид нормальной формы выражения - это применение встроенной функции с недостаточным числом переданных ей аргументов, например, (+ 1). Заметим, что этот последний вид нормальной формы выражения эквивалентен некоторому лямбдавыражению, находящемуся в нормальной форме. Так, например, выражение (+ 1) эквивалентно выражению (λx.+ 1 x). Теперь сходным образом можно определить и СЗНФ. Будем говорить, что выражение находится в СЗНФ, если оно имеет один из следующих видов:

константа c (в том числе - одна из встроенных функций);

переменная x;

лямбда-выражение λx.e, где e - произвольное выражение;

f e1 e2... ek, где f - встроенная функция с n аргументами, e1, e2,... ek - выражения в СЗНФ, причем k < n.

Теперь можно определить процесс "вычисления" выражения как процесс приведения его к СЗНФ с помощью β- и δ-редукций, применяемых в нормальном порядке. При таком процессе никогда не потребуется применять α-преобразований для "безопасного" переименования переменных. Таким образом можно считать, что у нас имеется достаточно простой и формальный процесс преобразования выражений лямбда-исчисления в простую форму с помощью только β- и δ-редукций.

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

Чистое лямбда-исчисление

Можно построить чистое лямбда-исчисление, т.е. последовательно выразить в виде лямбда-выражений все

константы

встроенные функции,

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

§ 8. Реализация логики в λ-исчислении

Пусть и – формулы. Вычислением формулы от формулы будем называть редукцию формулы ( )( ) к формуле в нормальной форме.

Пример. Рассмотрим формулы:

1)

( х.( y.x));

или

хy.x

2)

( х.( y.y));

или

хy.y

3)

z.z ( )( ),

 

где и – произвольные формулы в нормальной форме.

Первые две формулы не содержат свободных переменных, так что это некие объекты, ни от чего не зависящие, но и не редуцируемые друг к другу.

Проведем вычисление:

( z.z ( )( ))( х.( y.x)) ( х.( y.x))( )( ) ( y. )( )

А вот другое вычисление:

( z.z ( )( ))(( хy.y)) ( х.( y.y))( )( ) ( y.y)( )

Сравним наше вычисление с такой командой: Если z, то , иначе .

Как работает такая команда? Если z придать значение «истина», то результатом применения этой команды будет , если же z придать значение «ложь», то результатом применения этой команды будет . Это означает, что объект, описанный первой формулой, можно интерпретировать как Истина, объект, описанный второй формулой, –

как Ложь, а объект, описанный формулой zts.z(t)(s), интерпретировать как команду ветвления (формула под номером 3 отличается от этой

формулы тем, что еще выполнены подстановки вместо t и вместо s; как записать вычислением такую подстановку, наверно, понятно всем).

Чтобы все время не помнить, какой формулой описывается Истина, какой формулой – Ложь, а какой – ветвление, мы будем заменять эти формулы некоторыми именами и дальше в формулах использовать эти имен, понимая каждый раз, что реально вместо имени стоит эта самая формула. Присваивание имен будем обозначать так:

TRUE := ( хy.x);

FALSE := ( хy.y);

IF THEN ELSE := z. t. s.z(t)(s)

Латинские буквы, которые будут использоваться нами для записи имен, мы будем писать заглавными, чтобы отличать их от символов алфавита А.

Можно проверить (на практике), что

(IF THEN ELSE)(TRUE)( )( ) |– и (IF THEN ELSE)(FALSE)( )( ) |– .

Вот несколько новых имен, образованных с помощью уже введенных:

NOT := z.z(FALSE)(TRUE);

AND := s.( t.s(t)(FALSE)); OR := s.( t.s(TRUE)(t))

Можно проверить (на практике), что

(AND)(TRUE)(TRUE) |– TRUE; (AND)(TRUE)(FALSE) |– FALSE; (AND)(FALSE)(TRUE) |– FALSE; (AND)(FALSE)(FALSE) |– FALSE;

Отсюда, наверно, всем стало ясно, почему данной формуле присвоено такое имя.

Убедитесь, что имена NOT и OR тоже даны не случайно.

§ 9. Числа Черча

Натуральные числа представляют собой абстракцию подсчета тех или иных объектов. Для построения модели счета нужно выбрать, что мы будем считать. Вообще говоря, считать можно что угодно, при этом можно получить весьма различные модели арифметики, мы, однако, следуя Тьюрингу, будем подсчитывать, сколько раз некоторая функция применяется к своему аргументу. Это приводит нас к следующему определению натурального числа N. Число N представляется функцией с двумя аргументами, которая выполняет N-кратное применение своего первого аргумента ко второму. Более точно, сначала запишем определение для функции ZERO, представляющей число нуль, а затем покажем, как определяется число N+1, если число N уже определено. Тем самым будет возможно построить определение любого натурального числа N.

ZERO = λf.λx.x -- функция f ноль раз применяется к аргументу x, так что аргумент возвращается неизменным

(N+1) = λf.λx.f (N f x)

Следуя этому принципу определяем целые числа (числа Черча):

0=def f. x.x;

1=def f. x.f x;

2=def f. x.f (f x);

3=def f. x.f (f (f x));

и т. д.

А теперь еще одно имя:

NEXT =def s. f. x.s f (f x)

Вычислим:

(NEXT) (2) = ( s. f. x.s f (f x)) ( f. x.f (f x)) f. x. ( f. x.f (f x)) f (f x)

f. x.( x.f (f x)) (f x) f. x.f (f (f x)) = 3

Можно доказать, что (NEXT)(n) |– n + 1.

Алгоритмический анализ. Лекция 6.

λ-исчисление

§ 10. Понятие рекурсии

Мы уже знакомы с рекурсивными определениями, например, когда регламентируется способ построения простых выражений из констант, и при этом говорится, что полученные таким образом выражения могут рассматриваться в качестве базовых для построения более сложных по аналогичным законам. Здесь важно то, что определение ссылается на само себя.

В теории алгоритмов рекурсией называется алгоритм, в процессе выполнения которого он сам себя вызывает. Обычно говорят о рекурсивных функциях.

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

Пример: функция вычисления факториала.

Рекурсия может быть прямой (явной) или косвенной (неявной). В первом случае алгоритм сам себя вызывает. Во втором – алгоритм А вызывает алгоритм В, а тот в свою очередь вызывает А.

§ 11. Рекурсия в λ-исчислении

Вернемся к примеру функции вычисления факториала

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

Прежде всего, зададим эту функцию с помощью конструкций лямбда-исчисления, считая, что у нас имеются встроенные функции для вычитания (функция ''), умножения (функция '*') и сравнения с нулем (функция 'eq0'), кроме того, считаем, что у нас также есть функция 'if' для условного вычисления и константы '0' и '1'. Тогда определение функции будет выглядеть следующим образом.

fact = λn.if (eq0 n) 1 (* n (fact (– n 1)))

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

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

sFact = λfact.λn.if (eq0 n) 1 (* n (fact (- n 1)))

Конечно, эта новая функция не будет тем факториалом, который мы пытаемся построить хотя бы потому, что ее аргументом является не целое число, а некоторая функция, однако она некоторым вполне определенным образом связана с той функцией вычисления факториала, которую мы пытаемся построить. Поставим задачу найти такую функцию f, которая бы являлась неподвижной точкой определенной нами функции sFact, то есть такую f, что выполнено равенство

f = sFact f

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

f = sFact f = λn.if (eq0 n) 1 (* n (f (- n 1)))

Задача нахождения функции, эквивалентной заданной рекурсивной функции, сводится к задаче построения неподвижной точки для некоторой другой функции. Кажется, что эту задачу - задачу нахождения неподвижной точки - в общем виде решить не удастся. A priori вообще не ясно, всегда ли такая неподвижная точка существует. Однако оказывается, что удается построить функцию, которая для заданного аргумента вычисляет его неподвижную точку. Если обозначить через Y такую функцию нахождения неподвижной точки, то для любой функции

f должно быть справедливо равенство Y f = f(Y f).

Другими словами, результатом применения функции Y к функции f

должно быть такое значение x, что x = f(x). Одну из таких универсальных функций предложил Хаскелл Карри, которая в его честь называется Y-комбинатором Карри. Вот запись этой функции в нотации лямбда-исчисления:

Y = λh.(λx.h (x x))(λx.h (x x))

Проверим, действительно ли для этой функции выполнено

равенство Y f = f(Y f). Для этого запишем выражение Y f и попробуем привести его к СЗНФ. После того, как вместо Y будет подставлено

соответствующее лямбда-выражение, мы сможем выполнить один шаг β- редукции, и получим

(λx.f (x x))(λx.f (x x)).

f ((λx.f (x x))(λx.f (x x))).

Видно, что привести это выражение к СЗНФ вообще никогда не удастся, так как каждый следующий шаг редукции приводит только к увеличению длины выражения.

Однако, из проведенных шагов хорошо видно, что выражение Y f действительно эквивалентно выражению f(Y f), поскольку второе получается из первого за один шаг редукции.

Итак, мы получили, что выражение для рекурсивной функции можно получить, если построить некоторое вспомогательное выражение, а затем применить к нему Y-комбинатор Карри. Получившееся при этом выражение не может быть приведено к СЗНФ, однако оно все же будет работать как соответствующая рекурсивная функция. Давайте убедимся в этом, построив описанным способом функцию для вычисления факториала заданного числа, а затем применим ее к конкретному числу, скажем, числу 2, и проверим, как получается результат вычисления - число 2, равное fact(2). Для этого попробуем привести к СЗНФ выражение (Y sFact) 2, где Y обозначает Y-комбинатор Карри, а sFact - функцию, приведенную выше, полученную из рекурсивного определения факториала. Последовательные шаги по преобразованию выражения к СЗНФ показаны ниже.

Y sFact 2

sFact (Y sFact) 2

(λfact.λn.if (eq0 n) 1 (* n (fact (- n 1)))) (Y sFact) 2 (λn.if (eq0 n) 1 (* n ((Y sFact) (- n 1)))) 2

if (eq0 2) 1 (* 2 ((Y sFact) (- 2 1)))

(* 2 (Y sFact 1))

Мы видим, что последовательное выполнение β- и δ-редукций привело нас от выражения Y sFact 2 к выражению (* 2 (Y sFact 1)). Это уже показывает, что преобразование выражения происходит именно так, как ведет себя рекурсивное вычисление факториала. Продолжим преобразования.

Y sFact 2

sFact (Y sFact) 2

(λfact.λn.if (eq0 n) 1 (* n (fact (- n 1)))) (Y sFact) 2 (λn.if (eq0 n) 1 (* n ((Y sFact) (- n 1)))) 2

if (eq0 2) 1 (* 2 ((Y sFact) (- 2 1))) (* 2 (Y sFact 1))

(* 2 (sFact (Y sFact) 1)

(* 2 (λfact.λn.if (eq0 n) 1 (* n (fact (- n 1)))) (Y sFact) 1) (* 2 (λn.if (eq0 n) 1 (* n ((Y sFact) (- n 1)))) 1)

(* 2 (if (eq0 1) 1 (* 1 ((Y sFact) (- 1 1))))) (* 2 (* 1 (Y sFact 0)))

(* 2 (* 1 (sFact (Y sFact) 0)))

(* 2 (* 1 ((λfact.λn.if (eq0 n) 1 (* n (fact (- n 1)))) (Y sFact) 0))) (* 2 (* 1 ((λn.if (eq0 n) 1 (* n ((Y sFact) (- n 1)))) 0)))

(* 2 (* 1 (if (eq0 0) 1 (* 0 ((Y sFact) (- 0 1)))))) (* 2 (* 1 1))

2

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

Существуют и другие формы Y-комбинаторов, в частности, такие, которые применимы и при АПР, однако, все известные другие выражения для Y-комбинаторов выглядят сложнее, чем Y-комбинатор Карри.