Опубликован: 19.03.2004 | Уровень: специалист | Доступ: платный
Лекция 2:

Элементарный Лисп

< Лекция 1 || Лекция 2: 1234 || Лекция 3 >

Рекурсивные функции: определение и исполнение

8) Определения могут быть рекурсивными.

На практике такие определения обычно имеют глобальные имена, задаваемые с помощью функции DEFUN, о которой еще будет речь в конце этой лекции. Сейчас теоретически можно ограничиться конструктором локальных функций LABEL, которую мы вводим здесь для учебных целей (в системе GNU Clisp его нет, но для него проще определение интерпретатора, которое будем строить в следующей лекции).

Как правило, рекурсивные вызовы функций должны быть заданы в комплекте с нерекурсивными ветвями процесса. Основное применение условных выраженийрекурсивные определения функций .

(LABEL премьер          ;имя локальной функции
     (LAMBDA (x)          ;определение функции
          (COND ((ATOM x) x)
                      (T (премьер (CAR x)))
 )    )   )
Пример 2.3.

Новая функция "премьер" выбирает первый атом из любого данного. Если x является атомом, то он является результатом, иначе функцию "премьер" следует применить к первому элементу значения x, которое получается в результате вычисления формулы (CAR x). На составных x будет выполняться вторая ветвь, выбираемая по тождественно истинному значению встроенной константы   T.

Определение функции "премьер" рекурсивно. Эта функция действительно работает в терминах самой себя. Важно, что для любого S-выражения существует некоторое число применений функции CAR, после которого из этого S-выражения выделится какой-нибудь атом, следовательно, процесс вычисления функции всегда определен, детерминирован и завершится за конечное число шагов. Можно сказать, что для определенности рекурсивной функции следует формулировать условие ее завершения.

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

((LABEL премьер  
       (LAMBDA (x)
            (COND ((ATOM x) x)
	       (T (премьер (CAR x)))
  )    )    )    ; объявлена локальная функция "премьер"
        (QUOTE ((A . B) . C) )  ; дан аргумент локальной функции
)   ; завершено выражение с локальной функцией

LABEL вырабатывает представление функции, которое тут же может быть применено к аргументу. При этом LABEL дает имена обычным функциям, поэтому фактический параметр функции "премьер" будет вычислен до того, как начнет работать ее определение, и переменная x получит значение ((A . B) . C)).

x = ((A . B) . C))
Таблица 2.5. Схема вывода результата формы с рекурсивной функцией
Вычисляемая форма Очередной шаг Результат и комментарии
Вход в рекурсию
(премьер (QUOTE ((A . B) . C))) Выбор определения функции и (COND ((ATOM x) x) (T (премьер (CAR x))) )
Первый шаг рекурсии
  выделение параметров функции (QUOTE ((A . B) . C))
(QUOTE ((A . B) . C)) Вычисление аргумента функции x = ((A . B) . C)
(COND ((ATOM x)x) Перебор (ATOM x)
(T (премьер (CAR x))) )
предикатов: выбор первого  
(ATOM x) Вычисление первого предиката NIL = "ложь", т.к. x — не атом. Переход ко второму предикату
T Вычисление второго предиката T = "истина" — константа. Переход к выделенной ветви
Второй шаг рекурсии
(премьер (CAR x)) выделение параметров функции (CAR x)
(CAR x) Вычисление аргумента функции

x = (A . B).

Рекурсивный переход к редуцированному аргументу

(COND ((ATOM x) x) (T (премьер (CAR x))) ) Перебор предикатов: выбор первого (ATOM x)
(ATOM x) Вычисление предиката первого NIL = "ложь", т.к. x — не атом. Переход ко второму предикату
T Вычисление второго предиката T = "истина" — константа. Переход ко второй ветви
Третий шаг рекурсии
(премьер (CAR x)) выделение параметров функции (CAR x)
(CAR x) Вычисление аргумента функции

x = A.

Рекурсивный переход к редуцированному аргументу

(COND ((ATOM x) x) (T (премьер (CAR x))) ) Перебор предикатов: выбор первого (ATOM x)
(ATOM x) Вычисление первого предиката

T — т.к. x теперь атом.

Переход к первой ветви

x Вычисление значений переменной

A

Значение функции получено и вычисление завершено

Выход из рекурсии

Условные выражения не менее удобны и для численных расчетов:

(LABEL Абс 
     (LAMBDA (x)
            (COND
                ((< x 0 ) (- x))
                (T x) 
 )   )      )
Пример 2.4. Абсолютное значение числа
(LABEL Факториал 
     (LAMBDA (N)
         (COND
             ((= N 0 ) 1 )
             (T ( * N (Факториал (- N 1 ))) ) 
 )   )   )
Пример 2.5. Факториал неотрицательного числа

Это определение не завершается на отрицательных аргументах. Функция, определенная лишь для некоторых значений аргументов естественной области определения, называется частичной функцией.

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

(+ 1 2 3 4) = 10
(LABEL НОД 
     (LAMBDA (x y)
          (COND
                ((< x y) (НОД y x))
                ((= (остаток y x ) 0 ) x )
                (T (НОД (остаток y x) x )) 
 )   )    )
Пример 2.6. Алгоритм Евклида для нахождения наибольшего общего делителя двух положительных целых чисел (остаток [x, y] — функция, вычисляющая остаток от деления x на y).

Подробное изложение теории функций, определяемых рекурсивными выражениями, можно найти у многих математиков, например [19].

По мнению Дж. Маккарти, даже математики, включая логиков, термин "функция" используют не вполне аккуратно, применяя его к формулам вида "x + y^2" с уверенностью, что читатель, слушатель или собеседник правильно поймет, о чем идет речь. Автоматическая обработка данных, символизирующих функции, требует более точного различия между функциями и формами их применения. Для этих целей выработана система обозначений, известная как "лямбда-исчисление", которую предложил Алонсо Черч в работах по исследованию понятия "функция". Соответствующие рассуждения поясняются в книге по Lisp 1.5 [1] на простом примере (неофициальный перевод):

"Допустим, F — выражение, представляющее функцию двух целочисленных переменных. Необходима возможность приписывать к представлению функции ее аргументы так, чтобы однозначно понимать, каким будет результат применения функции. Традиционная форма F [3, 4] пригодна для функций, значение которых не зависит от порядка аргументов, например, если речь идет о сумме или произведении чисел: сумма [3, 4] = сумма [4, 3] = 7. Выражение "x + y^2 [3, 4]" не удовлетворяет такому требованию. Из его записи не ясно, 13 или 19 является его значением. Поэтому "x + y^2" лучше называть не функцией, а формой, которую можно превратить в функцию, если дать способ точного определения соответствия между переменными, входящими в форму, и аргументами описываемой функции. Именно это и выполняется с помощью так называемого "лямбда-конструктора". Любую форму E можно превратить в функцию, если объявить перечень ее формальных аргументов x1,x2,...,xk в виде лямбда-выражения (LAMBDA (x1 x2 ... xk) E). Аргументы такой функции следует перечислять в том же порядке, в каком они перечислены в лямбда-выражении. Например, (LAMBDA (x y) (x + y^2) ) — может работать как функция двух переменных и (LAMBDA (x y) (x + y^2) ) [3, 4] = 3 + 4^2 = 19, а (LAMBDA (y x) (x + y^2) ) [3, 4] = 4 + 3^2 = 13.

Переменные в лямбда-выражении называются номинальными или связанными, потому что их систематическая замена не меняет смысла функции. Таким образом, (LAMBDA (z t) (z + t^2) ) — это то же самое, что и (LAMBDA (x y) (x + y^2) ). Возможны выражения, в которых не все переменные связаны. Например, в функции двух переменных (LAMBDA (z t) (z^N + t^N) ) переменная N не связана. Это так называемая "свободная" переменная. Ее можно рассматривать как параметр. Если значение такого параметра не задано до попытки вычислить функцию, то значение функции должно быть не определено".

Соответствие между именем функции и ее определением можно задать с помощью более удобной специальной псевдо-функции   DEFUN, первый аргумент которой — имя функции, второй — список ее формальных параметров, третий — собственно тело определения функции. Формальным результатом   DEFUN, как и LABEL, является ее первый аргумент, который также становится объектом другой категории, но теперь это имя новой функции, известной во всей дальнейшей программе.

(DEFUN третий  ; имя новой функции
           (x)	       ; параметры функции
           (CAR (CDR (CDR x )))   ; тело функции
 )
Пример 2.7.

Новая функция "третий" действует почти так же, как и ранее приведенное определение, но теперь можно функцию рассматривать как глобальную и обращаться к ней в форме.

(третий (QUOTE (A B C)) ) ; — применение новой функции

Вышеописанные лямбда-обозначения не вполне удобны для именования рекурсивных функций, хотя это возможно. Название функции используется внутри рекурсивных определений, символизируя целое определение. При определении функции "премьер" удобнее использовать специальную функцию DEFUN, связывающую название функции и с определяющей формой, и со списком переменных.

(DEFUN премьер (x)
    (COND ((ATOM x) x)
             (T (премьер (CAR x)) ) 
 )  )

Собственно механизм связывания пока определен не был. Функция DEFUN использует лямбда-конструктор, чтобы представление функции получалось более естественно и использовалось как единая конструкция. Фактически LABEL и DEFUN реализуют аналог тождества:

премьер = (LAMBDA (x) (COND ((ATOM x) x)
                                             (T (премьер (CAR x)) ) ))

Роль локального связывания имени и определения функции в Лиспе выполняет специальная функция LABEL. Если EF — определение, а NF — его имя, то (LABEL NF EF) — функция, знающая свое имя NF. В форме (DEFUN премьер (x) (COND((ATOM x) x) (T (премьер (CAR x))) )) используется x — связанное имя переменной, а "премьер" — связанное имя функции, доступное программе.

Механизм конструирования определений функций на базе LABEL более логичен, чем DEFUN: наличие локального механизма формально пригодно и для реализации глобальных связей — они просто должны быть объявлены или расположены раньше всех локальных. Именно так и был формально определен реализационный базис чистого Лиспа для раскрутки полной Лисп-системы. Но на практике поначалу удобнее пользоваться функцией DEFUN, отдавая себе отчет в том, что это нечто вроде строительных лесов, помогающих создать более стройное здание. DEFUN совмещает работу LABEL и LAMBDA — сразу, как и в большинстве языков программирования, позволяет ввести и название функции, и имена ее арументов. Если LABEL строит именованную функцию для ее рекурсивного применения внутри текущего выражения, то DEFUN запоминает определение имени для вызова функции в любом месте программы.

Как и любая форма, любое S-выражение, символьные представления функций могут быть значениями аргументов — функциональные аргументы .

Базис элементарного Лиспа образуют пять функций над S-выражениями, CAR, CDR, CONS, ATOM, EQ, и четыре специальных функции, обеспечивающие управление программами и процессами и конструирование функциональных объектов QUOTE, COND, LAMBDA, LABEL.

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

Формально для перехода к самостоятельным упражнениям нужна несколько большая определенность по механизмам исполнения программ, представленных S-выражениями:

  • аргументы функций, как правило, вычисляются в порядке их перечисления;
  • композиции функций выполняются в порядке от самой внутренней функции наружу до самой внешней;
  • представление функции анализируется до того, как начинают вычисляться аргументы, т.к. в случае специальных функций аргументы можно не вычислять;
  • при вычислении лямбда-выражений связи между именами переменных и их значениями, а также между именами функций и их определениями, накапливаются в так называемом ассоциативном списке, пополняемом при вызове функции и освобождаемом при выходе из нее.
< Лекция 1 || Лекция 2: 1234 || Лекция 3 >
Дарья Федотова
Дарья Федотова
Сергей Березовский
Сергей Березовский

В рамках проф. переподготовки по программе "Программирование"

Есть курсы, которые я уже прошел. Но войдя в курс я вижу, что они не зачтены (Язык Ассемблера и архитектура ЭВМ, Программирование на С++ для профессионалов). Это как?

Алина Ленкова
Алина Ленкова
Россия, Ставрополь, СФ МГУПИ, 2014
Валерий Ромашов
Валерий Ромашов
Россия