Теперь вернемся к реализации декремента. Здесь будет использоваться трюк, чем-то похожий на оптимизацию динамики матрицами. А именно: для каждого числа n
мы будем поддерживать пару (n, n-1)
. От пары для числа n
: (n, n-1)
мы можем переходить к паре для числа n+1
: (n+1, n)
, преобразовывая пару (a, b)
по правилу (succ a, a)
. Именно так, а не (succ a, succ b)
— это важно.
Например: (2, 1)
→ (3, 2)
→ (4, 3)
→ ...
Теперь реализуем функцию для инкремента пары:
succ2 := \p. pair (succ (first p)) (first p)
Где-то здесь должен возникнуть вопрос: как это поможет реализовать декремент? Что ж, по-хорошему, мы бы сейчас написали что-то вроде
pred := \n. second (n succ2 (pair 0 -1))
То есть мы начинаем с пары (0, -1)
, затем n
раз ее инкрементируем, и тогда второй член пары содержал бы нужное значение. Единственная проблема — число -1
нельзя выразить в той форме, в которой мы представляем числа. Но здесь пригодится хитрое определение succ2
: оно игнорирует второй аргумент подаваемой ему пары. Значит, если вместо -1
написать что угодно, то декремент будет так же хорошо работать. Единственное, на что влияет этот параметр — на значение pred 0
. Для удобства условимся, что pred 0 -> 0
, так что окончательная реализация выглядит так:
pred := \n. second (n succ2 (pair 0 0))
Можно провести аналогию с таким циклом:
next, curr = 0, 0 repeat n times: curr = next next = next + 1 return curr
Ну и наконец вычитание. За O(a*b)
, ага.
sub := \a. \b. b pred a
Вообще, в λ-исчислении можно реализовать арифметику намного более оптимально. Но пока что нам важно выразить все понятия наиболее простым образом.
Пусть есть два числа a
и b
. Как понять, равны ли они, или одно больше другого? Здесь нам пригодится свойство pred 0 -> 0
. Отсюда следует, что iszero (sub a b)
тогда и только тогда, когда a - b <= 0
, иначе говоря, a <= b
. Соответственно:
le := \a. \b. iszero (sub a b)
Сравнение на "больше или равно" (greater than or equal to) симметрично:
ge := \a. \b. le b a
Сравнение на "меньше" выведем из того, что a < b
и a >= b
— противоположные утверждения.
lt := \a. \b. not (ge a b)
Наконец, сравнение на "больше":
gt := \a. \b. lt b a
Числа равны, если одно меньше или равно другого и наоборот:
eq := \a. \b. and (le a b) (le b a) ne := \a. \b. not (eq a b)
Реализацию деления с округлением вниз предлагаем в качестве упражнения читателю.
Если вкратце: проще всего реализовать деление x на d рекурсивно: если x < d
, то частное 0. Иначе — частное это (x - d) / d + 1
.
div := \x. \d. (lt x d) 0 (succ (div (sub x d) d))