На этом шаге мы рассмотрим примеры использования рекурсии по значению.
Мы будем говорить о рекурсии по значению, если вызов функции является выражением, определяющим результат функции.
(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.
На следующем шаге мы рассмотрим рекурсию по значению и по аргументам.