Автор: Пользователь скрыл имя, 18 Марта 2012 в 20:08, реферат
Функциональные языки программирования появились в качестве средства для написания программ, не содержащих в явном виде понятий ячеек памяти для хранения значений (переменных) и последовательности вычислений как процесса изменения состояния памяти. Тем не менее, основа для создания таких языков была предложена еще в середине 30-х годов 20-го века Алонзо Черчем (Alonzo Church) и Стефаном Клини (Stephen Kleene). Мы рассмотрим теорию Черча, названную им лямбда-исчислением, в качестве теоретической основы и "минимального" функционального языка программирования.
Ранее мы определяли нормальную форму как выражение, не содержащее внутри себя редексов. Если рассмотреть все возможные типы выражений, то мы увидим, что выражение, находящееся в нормальной форме может иметь один из следующих видов:
Последний вид нормальной формы выражения - это применение встроенной функции с недостаточным числом переданных ей аргументов, например, (+ 1). Заметим, что этот последний вид нормальной формы выражения эквивалентен некоторому лямбда-выражению, находящемуся в нормальной форме. Так, например, выражение (+ 1) эквивалентно выражению (λx.+ 1 x). Теперь сходным образом можно определить и СЗНФ. Будем говорить, что выражение находится в СЗНФ, если оно имеет один из следующих видов:
Теперь можно определить процесс "вычисления" выражения как процесс приведения его к СЗНФ с помощью β- и δ-редукций, применяемых в нормальном порядке. При таком процессе никогда не потребуется применять α-преобразований для "безопасного" переименования переменных. Таким образом можно считать, что у нас имеется достаточно простой и формальный процесс преобразования выражений лямбда-исчисления в простую форму с помощью только β- и δ-редукций. Такой процесс преобразования примерно соответствует исполнению программы в некотором простом языке функционального программирования. В дальнейшем мы покажем, как можно сравнительно простыми преобразованиями свести программу, написанную на языке, подобном языкуHaskell, к вычислению выражения, записанного средствами лямбда-исчисления, поэтому можно считать, что лямбда-исчисление является основой для реализации исполнения функциональных программ.
Выше мы уже отмечали,
что процесс преобразования формы
выражения при аппликативном
порядке редукций напоминает процесс
выполнения функциональной программы
при энергичном способе исполнения,
то есть при таком способе вызова
функций, когда перед началом
работы тела функции происходит вычисление
фактических аргументов и передача
полученных значений в функцию для
вычисления. Напротив, при нормальном
порядке применения редукций вычисления
происходят таким образом, что подстановка
аргументов происходит до того, как
будут вычислены их значения, что
примерно соответствует ленивой
схеме исполнения функциональной программы.
Однако, имеется и существенная разница
между ленивыми вычислениями и текстовыми
преобразованиями выражений так, как
это принято в лямбда-
Пусть требуется привести
к СЗНФ выражение (λx.* x x)(+ 2 3). В этом выражении имеются два
редекса, однако, если мы используем НПР,
то прежде всего выполняется β-редукция,
при которой выражение аргумента (+ 2 3) подставляетс
Расширим наше лямбда-исчисление, введя еще одну дополнительную конструкцию в качестве допустимого выражения. Новая конструкция будет иметь вид let x=e1 in e2, где x - переменная, а e1 и e2 - произвольные выражения. Новый вид выражения будет полностью эквивалентен выражению (λx.e2) e1, однако процесс его редуцирования будет происходить в соответствии с правилами ленивых вычислений, а именно, следующим образом. Данное выражение считается эквивалентным выражению e2, однако, если при его преобразовании потребуется произвести δ-редукцию, в которой одним из операндов встроенной функции является переменная x, упомянутая в заголовке выражения, то прежде всего выражение e1 приводится к СЗНФ, после чего результат подставляется вместо всех свободных вхождений переменной x в текущее выражение, после чего вычисления производятся обычным образом. Теперь модифицируем правило выполнения β-редукции: теперь при β-редукции выражение (λx.e) a будет преобразовываться в let x=a in e. Посмотрим, как теперь будут выполняться вычисления в НПР.
Снова рассмотрим исходное
выражение (λx.* x x)(+ 2 3). Теперь, однако, выполнение
β-редукции приведет нас к выражению let x = + 2 3 in (*
3.3. Чистое лямбда-исчисление
По сравнению с языками
функционального
В этом разделе мы постепенно
построим чистое лямбда-исчисление, последовательно
выразив в виде лямбда-выражений
все имевшиеся у нас ранее
константы и встроенные функции,
и задав с помощью тех же
лямбда-выражений все
Пусть у нас имеется функция, в определении которой есть прямое рекурсивное обращение к себе, например, такое, как определение функции вычисления факториала в языке Haskell.
fact n = if n == 0 then 1 else n * fact(n-1)
Прежде всего, зададим
эту функцию с помощью
fact = λn.if (eq0 n) 1 (* n (fact (- n 1)))
Здесь мы пока по-прежнему использовали
задание имени для функции fact
sFact = λfact.λn.if (eq0 n) 1 (* n (fact (- n 1)))
Конечно, эта новая функция не будет тем
факториалом, который мы пытаемся построить
хотя бы потому, что ее аргументом является
не целое число, а некоторая функция, однако
она некоторым вполне определенным образом
связана с той функцией вычисления факториала,
которую мы пытаемся построить. Зададимся
задачей найти такую функцию f, что она являетсянеподвижной точкой определенной
нами функции sFact, то есть такую f, что выполнено равенство f = sFact f.
f = sFact f = λn.if (eq0 n) 1 (* n (f (- n 1)))
Итак, задача нахождения функции,
эквивалентной заданной рекурсивной
функции, свелась к задаче построения
неподвижной точки для
Y = λh.(λx.h (x x))(λx.h (x x))
Проверим, действительно
ли для этой функции выполнено
равенство Y f = f(Y f). Для этого запишем выражение Y f и попробуем привести его к
СЗНФ. После того, как вместо Yбудет подставлено соответствующее
лямбда-выражение, мы сможем выполнить
один шаг β-редукции, и получим выражение (λx.f (x x))(λx.f (
Итак, мы получили, что выражение
для рекурсивной функции можно
получить, если построить некоторое
вспомогательное выражение, а затем
применить к нему Y-комбинатор Карри.
Получившееся при этом выражение
не может быть приведено к СЗНФ,
однако оно все же будет работать
как соответствующая
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-комбинатора Карри
Если несколько функций определяются таким образом, что в теле каждой из них имеются вызовы других функций из этого набора, то говорят, что мы имеем дело со взаимно-рекурсивными функциями. На самом деле этот случай можно свести к прямой рекурсии и, тем самым, выразить в лямбда-исчислении не только прямую, но и взаимную рекурсию. Для того, чтобы свести взаимную рекурсию к прямой, нам потребуются некоторые дополнительные встроенные функции. Во-первых, введем серию функций кортежирования, которые составляют кортеж из своих аргументов. Будем считать, что если k - некоторое натуральное число, то функция TUPLE-k имеет k аргументов и выдает в качестве результата кортеж из k элементов. Еще одна функция нужна для того, чтобы, наоборот, выделять элемент с заданным номером из кортежа. Назовем эту функцию ELEM. Функция ELEM имеет два аргумента: n - номер элемента кортежа - натуральное число, не превосходящее длины кортежа T, который является вторым аргументом этой функции. Результатом работы функции служит n-ый элемент кортежа T. Если число n - не натуральное или превосходит число элементов кортежа, заданного вторым аргументом, то результат работы функции не определен.
Теперь пусть имеются
определения взаимно-