Основы функционального программирования/Формализация функционального программирования на основе лямбда-исчисления

Материал из testwiki
Перейти к навигации Перейти к поиску

Шаблон:ОФП Содержание

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

Возникает вопрос: как учесть семантику встроенных функций при сравнении их экстенсионалов (так как явно определения этих функций неизвестны)? Варианты ответов:

  1. Можно попытаться выразить семантику встроенных функций при помощи механизма λ-исчисления. Этот процесс можно довести до случая, когда все встроенные функции не содержат непроинтерпретированных операций.
  2. Говорят, что f1 и f2 семантически равны (этот факт обозначается как f1=f2), если f1(x)=f2(x) при любой интерпретации непроинтерпретированных идентификаторов.

Формальная система представляет собой четвёрку:

P=V,Φ,A,R,

где Vалфавит;

Φ — множество правильно построенных формул;

Aаксиомы (при этом AΦ);

R — правила вывода.

В рассматриваемой задаче формулы имеют вид (t1=t2), где t1 и t2 — λ-выражения. Если некоторая формула выводима в формальной системе, то этот факт записывается как (t1=t2).

Говорят, что формальная система корректна, если (t1=t2)(t1=t2).

Говорят, что формальная система полна, если (t1=t2)(t1=t2).

Семантическое определение понятия «конструкция» (обозначение — Exp):

1°. vIdvExp.

2°. vId,EExpλv.EExp

3°. E,EExp(EE)Exp

4°. EExp(E)Exp

5°. Никаких других Exp нет.

Примечание: Id — множество идентификаторов.

Говорят, что v свободна в MExp, если:

1°. M=v.

2°. M=(M1M2), и v свободна в M1 или в M2.

3°. M=λv.M, и vv, и v свободна в M.

4°. M=(M), и v свободна в M.

Множество идентификаторов v, свободных в M, обозначается как FV(M).

Говорят, что v связана в MExp, если:

1°. M=λv.M, и v=v.

2°. M=(M1M2), и v связана в M1 или в M2 (то есть один и тот же идентификатор может быть свободен и связан в Exp).

3°. M=(M), и v связана в M.

Пример 26. Свободные и связанные идентификаторы.

  1. M=v. v — свободна.
  2. M=λx.xy. x — связана, y — свободна.
  3. M=(λv.v)v. v входит в это выражение как свободно, так и связанно.
  4. M=VW. V и W — свободны.

Правило подстановки: подстановка в выражение E выражения E вместо всех свободных вхождений переменной x обозначается как E[xE]. Во время подстановки иногда случается так, что получается конфликт имён, то есть коллизия переменных. Примеры коллизий:

(λx.yx)[yλz.z]=λx.(λz.z)x=λx.x — корректная подстановка;

(λx.yx)[yxx]=λx.(xx)x — коллизия имён переменных;

(λz.yz)[yxx]=λz.(xx)z — корректная подстановка.

Точное определение базисной подстановки:

1°. x[xE]=E.

2°. y[xE]=y.

3°. (λx.E)[xE]=λx.E.

4°. (λy.E)[xE]=λy.E[xE], при условии, что y∉FV(E).

5°. (λy.E)[xE]=(λz.E[yz])[xE], при условии, что yFV(E).

6°. (E1E2)[xE]=(E1[xE]E2[xE]).

Построение формальной системы

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

Правильно построенные формулы выглядят так: Exp=Exp.

Аксиомы:

λx.E=λy.E[xy]; (α)
(λx.E)E=E[xE]; (β)
t=t, в случае, если t — идентификаторы. (ρ)

Правила вывода:

t1=t2t1t3=t2t3; (μ)
t1=t2t3t1=t3t2; (ν)
t1=t2t2=t1; (σ)
t1=t2,t2=t3t1=t3; (τ)
t1=t2λx.t1=λx.t2. (ξ)

Пример 27. Доказать выводимость формулы (λx.xy)(λz.(λu.zu))v=(λv.yv)v

(λx.xy)(λz.(λu.zu))v=(λv.yv)v;
(μ): (λx.xy)(λz.(λu.zu))=(λv.yv);
(β): (λz.(λu.zu))y=(λv.yv);
(α): λu.yu=λv.yv — выводима.

Во втором варианте формализации функционального программирования можно воспользоваться не свойством симметричности отношения «=», а свойством несимметричности отношения «».

Во второй формальной системе правильно построенные формулы выглядят абсолютно так же, как и в первом варианте. Однако аксиомы принимают несколько иной вид:

λx.Mλy.M[xy] (α′)
(λx.M)NM[xN] (β′)
MM (ρ′)

Правило вывода во втором варианте формальной системы одно:

t1t1,t2t2t1t2t1t2 (π)

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

Определения:

  • Выражение вида λx.M называется α-редексом.
  • Выражение вида (λx.M)N называется β-редексом.
  • Выражения, не содержащие β-редексов, называются выражениями в нормальной форме.

Несколько теорем (без доказательств):

  • E1=E2E1E2E2E1.
  • EE1EE2F:E1FE2F. (Теорема Чёрча—Россера).
  • Если E имеет нормальные формы E1 и E2, то они эквивалентны с точностью до α-конверсии, то есть различаются только обозначением связанных переменных.

Стратегия редукции

1°. Нормальная редукционная стратегия (НРС). На каждом шаге редукции выбирается текстуально самый левый β-редекс. Доказано, что нормальная редукционная стратегия гарантирует получение нормальной формы выражения, если она существует.

2°. Аппликативная редукционная стратегия (АРС). На каждом шаге редукции выбирается β-редекс, не содержащий внутри себя других β-редексов. Далее будет показано, что аппликативная редукционная стратегия не всегда позволяет получить нормальную форму выражения.

Пример 28. Редукция выражения M=(λy.x)(EE), где E=λx.xx

1°. НРС: (λy.x)_(EE)=(λy.x)_[yEE]=x.

2°. АРС: (λy.x)(EE)_=(λy.x)((λx.xx)(λx.xx))_=(λy.x)((λx.xx)(λx.xx))_=.

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

Примечание: подчёркиванием выделен β-редекс, редуцируемый следующим шагом.

Пример 29. Редукция выражения M=(λx.xyxx)((λz.z)w)

1°. НРС: (λx.xyxx)_((λz.z)w)=((λz.z)w)_y((λz.z)w)((λz.z)w)=

=wy((λz.z)w)_((λz.z)w)=wyw((λz.z)w)_=wyww.

2°. АРС: (λx.xyxx)((λz.z)w)_=(λx.xyxx)_w=wyww.

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

Соответствие между вычислениями функциональных программ и редукцией

Работа интерпретатора описывается несколькими шагами:

  1. В выражении необходимо выделить некоторое обращение к рекурсивной или встроенной функции с полностью означенными аргументами. Если выделенное обращение к встроенной функции существует, то происходит его выполнение и возврат к началу первого шага.
  2. Если выделенное на первом шаге обращение к рекурсивной функции, то вместо него подставляется тело функции с фактическими параметрами (так как они уже означены). Далее происходит переход на начало первого шага.
  3. Если больше обращений нет, то происходит остановка.

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

Представление определений функций в виде λ-выражений

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

fact = λn.if n == 0 then 1 else n * fact (n – 1)

То же самое определение можно описать при помощи использования некоторого функционала:

fact = (λf.λn.if n == 0 then 1 else n * f (n – 1)) fact

Жирным шрифтом в представленном выражении выделен функционал F. Таким образом, функцию вычисления факториала можно записать так: fact = F fact. Можно видеть, что любое рекурсивное определение некоторой функции f можно представить в таком виде:

f = F f

Это выражение можно трактовать как уравнение, в котором рекурсивная функция f является неподвижной точкой функционала F. Соответственно интерпретатор функционального языка можно в таком же ключе рассматривать как численный метод решения этого уравнения.

Можно сделать предположение, что этот численный метод (то есть интерпретатор) в свою очередь может быть реализован при помощи некоторой функции Y, которая для функционала F находит его неподвижную точку (соответственно определяя искомую функцию) — f = Y F.

Свойства функции Y определяются равенством:

Y F = F (Y F)

Теорема (без доказательства):

Любой λ-терм имеет неподвижную точку.

λ-исчисление позволяет выразить любую функцию через чистое λ-выражение без использования встроенных функций. Например:

1°.

prefix = λxyz.zxy
head = λp.p(λxy.x)
tail = λp.p(λxy.y)

2°. Моделирование условных выражений:

True = λxy.x
False = λxy.y
if B then M else N = BNM, где B = {True, False}.