Перейти к содержанию

Основы функционального программирования/Доказательство свойств функций

Материал из Викиучебника — открытых книг для открытого мира

Формальная задача: пусть имеется набор функций f=⟨f1,…,fn⟩, определённых на областях D=⟨D1,…,Dn⟩. Требуется доказать, что для любого набора значений d имеет место некоторое свойство, то есть:

∀d∈D:P(f(d)),

где P — рассматриваемое свойство. Например:

∀d∈D:f(d)⩾0

∀d∈D:f(d)=f(f(d))

∀d∈D:f(d)=f1(d)

Вводится принципиальное ограничение на рассматриваемые свойства — свойства только тотальные, то есть справедливые для всей области D.

Далее рассматриваются некоторые виды областей определения D.

1. D — линейно упорядоченное множество.

Полуформально линейно упорядоченное множество можно определить как такое множество, для каждых элементов которого можно сказать, какой меньше (или больше), либо они равны, то есть:

∀d1,d2∈D:(d1⩾d2)∨(d1⩽d2).

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

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

  1. P(f(∅)) — базис индукции;
  2. ∀d1,d2∈D, d1⩽d2:P(f(d1)) — шаг индукции.

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

2. D — определяется как индуктивный класс

Из прошлой лекции известно, что индуктивный класс определяется через ввод базиса класса (это либо набор каких либо констант di=0,n∈D, либо набор первичных типов Ai=0,n:d∈Ai⇒d∈D. Также индуктивный класс определяется при помощи шага индукции — заданы конструкторы g1,…,gm, определённые над Ai и D, и справедливо, что:

(ai∈Ai)∧(xj∈D)⇒gk(ai,xj)∈D, k=1,m‾.

В этом случае доказательство свойств функций также резонно проводить в виде индукции по даным. Метод индукции по даным в этом случае также очень прост:

  1. P(f(d)) необходимо доказать для базиса класса;
  2. Шаг индукции: P(f(d))=P(f(gi(d))).

Например, для доказательства свойств функций для списков (тип List⁡(A)), достаточно доказать рассматриваемое свойство для двух следующих случаев:

  1. P(f([])).
  2. ∀a∈D,∀L∈List⁡(A):P(f(L))⇒P(f(a:L)).

Доказательство свойств функций над S-выражениями (тип S-expr⁡(A)) можно проводить на основе следующей индукции:

  1. ∀a∈A:P(f(a)).
  2. ∀x,y∈S-expr⁡(A):P(f(x))∧P(f(y))⇒P(f(x:y)).

Пример 23. Доказать, что ∀L∈List⁡(A):L∗[]=L.

Для доказательства этого свойства можно использовать только определение типа List⁡(A) и самой функции append (в инфиксной записи используется символ ∗).

  1. L=[]:[]∗[]=[]=L. Базис индукции доказан.
  2. ∀L∈List⁡(A):L∗[]=L. Теперь пусть применяется конструктор: a:L. Необходимо доказать, что (a:L)∗[]=a:L. Это делается при помощи применения второго клоза определения функции append:

(a:L)∗[]=a:(L∗[])=a:(L)=a:L.

Пример 24. Доказать ассоциативность функции append.

То есть необходимо доказать, что для любых трёх списков L1, L2 и L3 имеет место равенство (L1∗L2)∗L3=L1∗(L2∗L3). При доказательстве индукция будет проводиться по первому операнду, то есть списку L1:

  1. L1=[]:

([]∗L2)∗L3=(L2)∗L3=L2∗L3.

[]∗(L2∗L3)=(L2∗L3)=L2∗L3.

  1. Пусть для списков L1, L2 и L3 ассоциативность функции append доказана. Необходимо доказать для (a:L1), L2 и L3:

((a:L1)∗L2)∗L3=(a:(L1∗L2))∗L3=a:((L1∗L2)∗L3).

(a:L1)∗(L2∗L3)=a:(L1∗(L2∗L3)).

Как видно, последние два выведенных выражения равны, так как для списков L1, L2 и L3 ассоциативность полагается доказанной.

Пример 25. Доказательство тождества двух определений функции reverse.

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

reverse⁡ []=[]

reverse⁡ (H:T)=(reverse⁡ T)∗[H]

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

reverse′⁡L=rev⁡ L[]

rev⁡ [] L=L

rev⁡ (H:T) L=rev⁡ T (H:L)

Видно, что первое определение функции обращения списков — это обычное рекурсивное определение. Второе же определение использует аккумулятор. Требуется доказать, что:

∀L∈List⁡(A):reverse⁡ L=reverse′⁡L.

1. Базис — L=[]:

reverse⁡ []=[].

reverse′⁡[]=rev⁡ [] []=[].

2. Шаг — пусть для списка L тождество функций reverse и reverse′⁡ доказано. Необходимо доказать его для списка (H:L).

reverse⁡ (H:L)=(reverse⁡ L)∗[H]=(reverse′⁡L)∗[H].

reverse′⁡(H:L)=rev⁡ (H:L) []=rev⁡ L (H:[])=rev⁡ L [H].

Теперь необходимо доказать равенство двух последних выведенных выражений для любых списков над типом A. Это также делается по индукции:

2.1. Базис — L=[]:

(reverse′⁡[])∗[H]=(rev⁡ [][])∗[H]=[]∗[H]=[H].

rev⁡ [] [H]=[H].

2.2. Шаг — L=(A:T):

(reverse′⁡(A:T))∗[H]=(rev⁡ (A:T) [])∗[H]=(rev⁡ T (A:[]))∗[H]=(rev⁡ T [A])∗[H].

rev⁡ (A:T) [H]=rev⁡ L (A:H).

Здесь произошло выпадение в дурную бесконечность. Если дальше пытаться проводить доказательство по индукции для новых выведенных выражений, то эти самые выражения будут всё усложняться и усложняться. Но это не причина для того, чтобы отчаиваться, ибо доказательство всё равно можно провести. Надо просто придумать некую «индукционную гипотезу», как это было сделано в предыдущем примере.

Индукционная гипотеза: (reverse′⁡L1)∗L2=rev⁡ L1 L2. Эта индукционная гипотеза является обобщением выражения (reverse′⁡L)∗[H]=rev⁡ L [H].

Базис индукции для этой гипотезы очевиден. Шаг индукции в применении к выражению в пункте 2.2 выглядит следующим образом:

(reverse′⁡(A:T))∗L2=(rev⁡ (A:T) [])∗L2=(rev⁡ T [A])∗L2=

=((reverse′⁡T)∗[A])∗L2=(reverse′⁡T)∗([A]∗L2)=(reverse′⁡T)∗(A:L2).

rev⁡ (A:T) L2=rev⁡ T (A:L2)=(reverse′⁡T)∗(A:L2).

Что и требовалось доказать.

Общий вывод: в общем случае для доказательства свойств функций методом индукции может потребоваться применение некоторых эвристических шагов, а именно введение индукционных гипотез. Эвристический шаг — это формулирование утверждения, которое ниоткуда не следует. Таким образом, доказательство свойств функций есть своего рода творчество.