Автор: Пользователь скрыл имя, 18 Марта 2012 в 20:08, реферат
Функциональные языки программирования появились в качестве средства для написания программ, не содержащих в явном виде понятий ячеек памяти для хранения значений (переменных) и последовательности вычислений как процесса изменения состояния памяти. Тем не менее, основа для создания таких языков была предложена еще в середине 30-х годов 20-го века Алонзо Черчем (Alonzo Church) и Стефаном Клини (Stephen Kleene). Мы рассмотрим теорию Черча, названную им лямбда-исчислением, в качестве теоретической основы и "минимального" функционального языка программирования.
f1 = F1(f1, f2,... fn)
f2 = F2(f1, f2,... fn)
...
fn = Fn(f1, f2,... fn)
где все Fi - выражения, в которых встречаются рекурсивные обращения к определяемым функциям f1, f2,... fn. Прежде всего образуем новое выражение, представляющее собой кортеж, в котором собраны все определения функций:
T = TUPLE-n F1(f1, f2,... fn) F2(f1, f2,..
каждая из определяемых функций f1, f2,... fn может быть теперь выражена с помощью выделения соответствующего элемента этого кортежа:
fi = ELEM i T
поэтому если мы теперь подставим в определение кортежа T вместо всех вхождений fi их выражения в виде элемента кортежа, то мы получим рекурсивное определение для кортежа T с прямой рекурсией:
T = TUPLE-n F1((ELEM 1 T),... (ELEM n T))... Fn((ELEM 1 T),... (ELEM n T))
Кортеж T теперь может быть представлен как и раньше с помощью Y-комбинатора
Y (λT.TUPLE-n F1((ELEM 1 T),... (ELEM n T))... Fn((ELEM 1 T),... (ELEM n T)))
а каждая из отдельно взятых функций может быть получена в виде элемента этого кортежа.
Теперь попробуем представить в виде лямбда-выражений все стандартные константы и функции, использованные нами ранее. Для этого нам надо будет выбрать представление и разработать технику выполнения операций над целыми числами, логическими значениями и списками. Основным критерием для выбора того или иного представления будет функциональность этого представления, то есть нам надо, чтобы все представляемые нами константы и стандартные функции "вели себя правильно", в соответствии с нашими представлениями о результатах выполнения операций. Проще всего определить логические константы и операции над ними, поскольку таких констант всего две - истина и ложь, а операции над ними подчиняются очень простым законам. С этого и начнем.
Основное назначение логических значений состоит в том, чтобы организовать выбор в ходе вычислений, поэтому начать следует с того, как можно реализовать функцию выбора IF. Эта функция имеет три аргумента, при этом первым аргументом как раз и является логическое значение. Если логическое значение надо представлять некоторой функцией, то проще всего сделать так, чтобы само это логическое значение и отвечало за выбор альтернативы при выполнении функции IF. То есть реализуем функцию так, чтобы она просто применяла свой первый аргумент к остальным двум, а уже этот аргумент отбрасывал бы один из аргументов и оставлял второй. Тогда реализация констант TRUE и FALSE, а также функция IF получают следующее представление.
IF = λp.λt.λe.p t e
TRUE = λx.λy.x
FALSE = λx.λy.y
Проверим, что каковы бы ни
были выражения A и B, выражение IF
IF TRUE A B
(λp.λt.λe.p t e) (λx.λy.x) A B
(λt.λe.(λx.λy.x) t e) A B
(λe.(λx.λy.x) A e) B
(λx.λy.x) A B
(λy.A) B
A
Разумеется, совершенно аналогично выражение IF FALSE A B будет преобразовано в B.
Над логическими значениями TRUE и
AND = λx.λy.x y FALSE
OR = λx.λy.x TRUE y
NOT = λx.x FALSE TRUE
Здесь в определении новых
операций использованы уже определенные
ранее константы TRUE и FALSE.
Мы ограничимся только
арифметикой натуральных чисел,
все остальные числа - рациональные,
вещественные, комплексные и др.
можно получить, комбинируя натуральные
числа и определяя
ZERO = λf.λx.x -- функция f ноль раз применяется к аргументу x, так что аргумент возвращается неизменным
(N+1) = λf.λx.f (N f x)
Заметим, кстати, что константа ZERO определена в точности так же, как константа FALSE, однако далеко идущих выводов из этого сходства делать все же не следует. Из приведенного определения целых чисел немедленно следует определение функции следования, которая добавляет единицу к своему аргументу:
SUCC = λn.λf.λx.f (n f x)
Теперь нетрудно также определить операции сложения и умножения целых чисел. Так, например, можно сказать, что для того, чтобы сложить два числа m и n, надо m раз увеличить число nна единицу. Аналогично, чтобы умножить m на n, надо m раз применить функцию увеличения на n к нулю. Отсюда следуют определения:
PLUS = λm.λn.m SUCC n
MULT = λm.λn.m (PLUS n) ZERO
К сожалению, определить операции вычитания в представленной нами арифметике совсем не так просто. Прежде всего определим функцию PRED, которая выдает предыдущее натуральное число для всех чисел, больших нуля, а для аргумента, равного нулю, выдает также ноль. Одно из возможных определений такой функций выглядит следующим образом:
PRED = λn.λf.λx.n (λg.λh.h (g f)) (λu.x) (λu.u)
Проверьте, что применение
этой функции к значению ZERO действ
MINUS = λm.λn.n PRED m
Арифметику целых чисел можно теперь связать с логическими значениями, определив функции сравнения чисел. Проще всего определить функцию сравнению числа с нулем. Вот одно из возможных определений для такой функции.
EQ0 = λn.n (λx.FALSE) TRUE
Очевидно, что если применить функцию к значению ZERO, то функция (λx.FALSE) не будет применена ни разу к своему аргументу, поэтому результатом применения функции будет значение TRUE. Если же значение аргумента отлично от нуля, то функция (λx.FALSE) после первого же применения выдаст значение FALSE, и в дальнейшем, сколько бы раз ее ни применять, выдаваемым значением так и будет оставаться FALSE. Теперь можно определить функции сравнения двух чисел с помощью операций GE ("больше или равно") и LE ("меньше или равно"), используя только что определенную операцию сравнения с нулем и операцию вычитания натуральных чисел MINUS.
GE = λm.λn.EQ0 (MINUS m n)
LE = λm.λn.EQ0 (MINUS n m)
Остальные операции сравнения легко выражаются через эти операции сравнения и уже определенные ранее логические операции:
GT = λm.λn.NOT (LE m n)
LT = λm.λn.NOT (GE m n)
EQ = λm.λn.AND (GE m n) (LE m n)
NEQ = λm.λn.NOT (EQ m n)
Теперь, когда уже определена арифметика и логика, давайте, вооруженные опытом описания различных стандартных функций построим функции для формирования составных значений, подобных кортежам или спискам. Представляемые нами составные значения будут больше всего напоминать списки, как они определены в языке LISP, то есть списки элементов самого разного типа (элементом такого списка может быть любое значение, в том числе другой список, число, логическое значение или, вообще говоря, любая функция). Для создания таких списков необходимо определить одно базовое значение - пустой список - и функции, позволяющие из двух заданных значений создавать их пару (функция CONS), а также выделять первый и второй элемент пары (функции HEAD и TAIL). Для того, чтобы можно было исследовать списки в программах, требуется определить также по крайней мере одну логическую функцию NULL, которая выдает значениеTRUE или FALSE в зависимости от того, является ли ее аргумент пустым списком или нет.
Функция CONS может соединять в пару два своих аргумента, просто создавая функцию, которая будет применять свой аргумент к обеим частям пары как к двум своим аргументам. Другими словами, если H и T - два произвольных значения, то их пару можно представить функцией (λs.s H T). Таким образом, функция CONS получает следующее определение:
CONS = λh.λt.λs.s h t
Для того, чтобы из составного
значения (λs.s H T) выделить первый элемент H, надо применить эту функцию
к такому значению s, которое выдаст первый из двух
своих аргументов. Таким значением является
уже определенная нами ранее константа TRUE. Ясно, что результатом применения (λs.s H T) TRUE буд
HEAD = λl.l TRUE
Разумеется, функция будет работать "правильно", только если ее аргументом будет пара, скажем, составленная с помощью функции CONS. Аналогичным образом определяется и функцияTAIL.
TAIL = λl.l FALSE
Функция NULL должна выдавать в качестве
результата FALSE для любой пары вида (λs.s H T). Поэтому можно определить
эту функцию таким образом, чтобы она применяла
свой аргумент к функции, выдающей значение FALSE для любых двух аргументов: λh.λt.FALSE.
NULL = λl.l (λh.λt.FALSE)
Однако, та же функция должна выдавать в качестве результата TRUE, если ее аргументом будет пустой список. Отсюда можно легко вывести простой способ представления пустого спискаNIL:
NIL = λx.TRUE
Давайте проверим, что наши
определения работают, на простом
примере: проверим, что значением
выражения NULL (HEAD (CONS
NULL (HEAD (CONS NIL A))
(λl.l (λh.λt.FALSE)) (HEAD (CONS
(HEAD (CONS NIL A)) (λh.λt.FALSE)
((λl.l TRUE) (CONS NIL A)) (λh.λt.FALSE)
((CONS NIL A) TRUE) (λh.λt.FALSE)
(((λh.λt.λs.s h t) NIL A) TRUE) (λh.λt.FALSE)
((λs.s NIL A) TRUE) (λh.λt.FALSE)
(TRUE NIL A) (λh.λt.FALSE)
NIL (λh.λt.FALSE)
(λx.TRUE) (λh.λt.FALSE)
TRUE
Мы получили ожидаемый результат. Подобным же образом можно представить в чистом лямбда-исчислении любое выражение, в том числе содержащее рекурсивные вызовы (применяя преобразование, использующее Y-комбинатор). Таким образом, можно сказать, что мы получили возможность эквивалентного представления любой функциональной программы, написанной в нашем расширенном лямбда-исчислении, с помощью лишь средств чистого лямбда-исчисления.
На самом деле использовать
чистое лямбда-исчисление в практических
целях, конечно же, неудобно. Даже для
того, чтобы сложить числа 2 и 5, потребуется
выполнить огромное количество редукций,
а реализация рекурсии в виде применения Y-комбинатора
- это тоже далеко не самый простой способ
выполнения рекурсивных функций. Поэтому
в дальнейшем мы будем использовать только
расширенное лямбда-исчисление, содержащее
встроенные константы и функции, а также
конструкцию let для эффективной реализации
механизма ленивых вычислений. Более того,
мы еще расширим наше лямбда-исчисление
для того, чтобы явно представить в нем
рекурсию, что позволит нам обойтись без
применения Y-комбинатора. Новая конструкция
по виду и по действию будет напоминать
конструкцию let, разница будет состоять только
в том, что в новой конструкции letrec x = e1 in
Рассмотрим правила редукций, применяемые в условиях существования рекурсивных определений, на примере преобразования выражения
letrec FACT = λn.IF (EQ0 n) 1 (MULT n (FACT (PRED n))) in FACT 2
letrec FACT = λn.IF (EQ0 n) 1 (MULT n (FACT (PRED n))) in (λn.IF (EQ0 n) 1 (MULT n (FACT (PRED n)))) 2
letrec FACT = λn.IF (EQ0 n) 1 (MULT n (FACT (PRED n))) in IF (EQ0 2) 1 (MULT 2 (FACT (PRED 2)))
letrec FACT = λn.IF (EQ0 n) 1 (MULT n (FACT (PRED n))) in MULT 2 (FACT 1)
letrec FACT = λn.IF (EQ0 n) 1 (MULT n (FACT (PRED n))) in MULT 2 ((λn.IF (EQ0 n) 1 (MULT n (FACT (PRED n)))) 1)
letrec FACT = λn.IF (EQ0 n) 1 (MULT n (FACT (PRED n))) in MULT 2 (IF (EQ0 1) 1 (MULT 1 (FACT (PRED 1))))
letrec FACT = λn.IF (EQ0 n) 1 (MULT n (FACT (PRED n))) in MULT 2 (MULT 1 (FACT 0))
letrec FACT = λn.IF (EQ0 n) 1 (MULT n (FACT (PRED n))) in MULT 2 (MULT 1 ((λn.IF (EQ0 n) 1 (MULT n (FACT (PRED n)))) 0))
letrec FACT = λn.IF (EQ0 n) 1 (MULT n (FACT (PRED n))) in MULT 2 (MULT 1 (IF (EQ0 0) 1 (MULT 0 (FACT (PRED 0)))))
letrec FACT = λn.IF (EQ0 n) 1 (MULT n (FACT (PRED n))) in MULT 2 (MULT 1 1)
MULT 2 (MULT 1 1)
2
В этом примере в конструкции letrec
В следующей главе мы будем использовать наше расширенное лямбда-исчисление в роли простого языка функционального программирования для того, чтобы более точно исследовать семантику исполнения функциональных программ. При этом мы не будем заниматься чисто текстовыми преобразованиями выражений, как мы это проделывали на протяжении всей этой главы. Вместо этого мы будем использовать более традиционные для программирования способы хранения и преобразования значений.
http://starling.rinet.ru/~
В
этой главе мы вспомним определение
и некоторые базовые свойства б
В
середине 60-х годов Питер Ландин
отметил, что сложный язык программирования
можно изучать, сформулировав его
ядро в виде небольшого базового исчисления,
которое выражает самые существенные
механизмы языка, и дополнив его набором
удобных производных форм (derived forms), поведение которых можно выразить
путем перевода на язык базового исчисления
(Landin (1964),Landin (1965), Landin (1966); см. также Tennent (1981)). В качестве базового языка Ландин использовал лямбда-исчисление