На этом шаге мы рассмотрим примеры использования рекурсии по значению.
Мы будем говорить о рекурсии по значению, если вызов функции является выражением, определяющим результат функции.
(DEFUN MEMBER1 (LAMBDA (A L)
(COND ( (NULL L) NIL )
( (EQ (CAR L) A) L )
( T (MEMBER1 A (CDR L)))
)
))
В языках императивного программирования существует возможность использования вспомогательных переменных, в которых можно сохранять промежуточные результаты. В функциональном программировании переменные таким образом не используются. Но соответствующий механизм можно легко осуществить, используя вспомогательную функцию, у которой нужные вспомогательные переменные являются параметрами.
Тогда для функции REVERSE мы получим такое определение:
(DEFUN REVERSE (LAMBDA (L)
(TRANCE L NIL)
))
; ----------------------------
(DEFUN TRANCE (LAMBDA (L Result)
(COND ( (NULL L) Result )
( T (TRANCE (CDR L) (CONS (CAR L) Result)) )
)
))
Вспомогательная функция TRANCE рекурсивна по значению. С помощью этой функции элементы переносятся таким образом, что на каждом шаге рекурсии очередной элемент переходит из аргумента L в аргумент Result.
(DEFUN NTH (LAMBDA (I LST)
(COND ( (EQ I 1) (CAR LST) )
( T (NTH (- I 1) (CDR LST)) )
)
))
(DEFUN MEMBER2 (LAMBDA (X LST)
(COND ( (NULL LST) NIL)
( T (OR (COND ( (ATOM (CAR L))
(EQ X (CAR L)) )
( T (MEMBER2 X (CAR L)))
)
(MEMBER2 X (CDR L))
)
)
)
))
(DEFUN COPY (LAMBDA (LST M)
(COND ( (NULL LST) NIL )
( (ATOM LST) (POISK LST M) )
( T (CONS (COPY (CAR LST) M)
(COPY (CDR LST) M)) )
)
))
; ------------------------
(DEFUN POISK (LAMBDA (X LST)
; Нахождение "глубины погружения" X в список LST
(COND ( (MEMBER X LST) 1 )
( (MEMBER2 X (CAR LST))
(+ 1 (POISK X (CAR LST))) )
( T (POISK X (CDR LST)) )
)
))
; --------------------------
(DEFUN MEMBER2 (LAMBDA (X LST)
; Предикат MEMBER2 устанавливает вхождение
; элемента X в многоуровневый список LST
(COND ( (NULL LST) NIL)
( T (OR (COND ( (ATOM (CAR LST))
(EQ X (CAR LST)) )
( T (MEMBER2 X (CAR LST)) )
)
(MEMBER2 X (CDR LST))) )
)
))
Определение функционального предиката (Exist P L), который истинен, когда предикат P истинен хотя бы для одного элемента списка L.
(DEFUN ALL (LAMBDA (P L)
(COND ( (NULL L) T )
( (P (CAR L)) (ALL P (CDR L)))
( T NIL )
)
))
; ----------------------
(DEFUN EXIST (LAMBDA (P L)
(COND ( (NULL L) NIL )
( (P (CAR L)) T )
( T (EXIST P (CDR L)) )
)
))
0 - 1 --> 0
(n+1) - 1 --> n
Черч так и не справился с этой задачей и уже уверился в неполноте своего исчисления,
но в 1932 г. Стефан Клини, тогда молодой аспирант, предложил следующую, на первый
взгляд искусственную, но на самом деле полностью отвечающую сути дела конструкцию [1, с.6]:
F (x,y,z) = если x=0
то 0
иначе если y+1=x
то z
иначе F(x,y+1,z+1)
Тогда n-1 = F (n,0,0).
Приведем функцию, решающую данную проблему:
(DEFUN DIFUNIT (LAMBDA (X Y Z)
(COND ( (EQ X 0) 0 )
( (EQ (+ Y 1) X) Z )
( T (DIFUNIT X (+ Y 1) (+ Z 1)) )
)
))
(DEFUN PLUS1 (LAMBDA (A B)
(COND ( (EQ B 0) A )
( (PLUSP B) (PLUS1 (+ A 1) (- B 1)) )
( T (PLUS1 (- A 1) (+ B 1)) )
)
))
; ----------------------
(DEFUN PLUS2 (LAMBDA (A B)
; Моделирование сложения натуральных чисел
(COND ( (EQ B 0) A )
( (PLUSP B) (PLUS2 (+ A 1) (- B 1)) )
( T NIL )
)
))
(DEFUN FIRSTATOM (LAMBDA (L)
(COND ( (ATOM L) L )
( T (FIRSTATOM (CAR L)) )
)
))
(DEFUN DELETEN (LAMBDA (N LST)
(COND ( (EQ N 0) LST )
( T (DELETEN (- N 1) (CDR LST)) )
)
))
(DEFUN LAST (LAMBDA (L)
(COND ( (NULL L) NIL )
( (NULL (CDR L)) (CAR L) )
( T (LAST (CDR L)) )
)
))
(DEFUN ATOMLIST (LAMBDA (X)
(COND ( (NULL X) T )
( (ATOM X) NIL )
( (ATOM (CAR X)) (ATOMLIST (CDR X)) )
( T NIL )
)
))
(DEFUN APPEND (LAMBDA (X Y)
(COND ( (NULL X) Y )
( T (CONS (CAR X) (APPEND (CDR X) Y)) )
)
))
(DEFUN APPEND (LAMBDA (X Y)
(COND ( (NULL X) Y )
( T (APPEND (CDR X) (CONS (CAR X) Y)) )
)
))
Заметим, что если список X очень длинный, то вычисления будут долгими. Создание списочных ячеек с помощью функции COND требует времени и в будущем добавляет работы "мусорщику". Если, например, список X содержит 1000 элементов, а список Y - один элемент, то во время вычисления будет создано 1000 новых ячеек, хотя вопрос состоит лишь в добавлении одного элемента к списку. Если бы последовательность аргументов была бы другой, то создалась бы одна ячейка, и списки были бы объединены приблизительно в 1000 раз быстрее.
Если для нас несущественно, что значение переменной X изменится, то мы можем вместо функции APPEND использовать более "быструю" функцию NCONC. Функция NCONC делает то же самое, что и функция APPEND, с той лишь разницей, что она просто объединяет списки, изменяя указатель в поле CDR последней ячейки списка, являющегося первым аргументом, на начало списка, являющегося вторым аргументом. Использовав функцию NCONC, мы избежим копирование списка X, однако в качестве побочного эффекта изменилось значение переменной X. Так же изменятся и все другие структуры, использовавшие ячейки первоначального значения переменной X.
На следующем шаге мы рассмотрим рекурсию по значению и по аргументам.