Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
практика 6-10 401-ТИ Блінова Настя.docx
Скачиваний:
0
Добавлен:
01.04.2025
Размер:
36.79 Кб
Скачать

8. Рекурсія.

Основна ідея рекурсивного визначення у тому, що функцію можна з допомогою рекуррентных формул зводити до деяким початковим значенням, до раніше певним функцій або до самої обумовленою функції, але з більш «простими» аргументами. Обчислення такий функції закінчується той момент, як його зводиться до відомим початковим значениям.

Рекурсивна процедура, по-перше містить за крайнього заходу одну термінальну гілка і умова закінчення. По-друге, коли процедура доходить до рекурсивної галузі, то функціонуючий процес припиняється, і новий такий процес запускається спочатку, але вже новий рівень. Перерваний процес якимось чином запам'ятовується. Він чекати, і почне виконуватися лише по закінченні нового процесу. Натомість, новий процес може призупинитися, очікувати й т. д.

Говоритимемо про рекурсії за значенням і рекурсії по аргументів. У першому випадку виклик є вираженням, визначальним результат функції. У другому - як результату функції повертається значення деякою інший функції і рекурсивний виклик бере участь у обчисленні аргументів цієї функції. Аргументом рекурсивного виклику то, можливо знову рекурсивний виклик і такі викликів то, можливо много.

Розглянемо такі форми рекурсії: проста рекурсія; паралельна рекурсія; взаємна рекурсия.

Рекурсія називається простий, якщо виклик функції є у деякою галузі лише одне раз. Простий рекурсії в процедурному програмуванні відповідає звичайний цикл.

Наприклад напишемо функцію обчислення чисел Фібоначчі (F(1)=1; F(2)=1; F(n)=F(n-1)+F(n-2) при n>2):

(DEFUN FIB (N)

(IF (> N 0)

(IF (OR N=1 N=2) 1

(+ (FIB (- N 1)) (FIB (- N 2))))

ОШИБКА_ВВОДА))

Рекурсію називають паралельної, якщо вона зустрічається одночасно у кількох аргументах функції:

(DEFUN f ...

...(g ... (f ...) (f ...) ...)

...)

Розглянемо використання паралельної рекурсії з прикладу перетворення списковою структури в однорівневий список:

(DEFUN PREOBR (L)

(COND

((NULL L) NIL)

((ATOM L) (CONS (CAR L) NIL))

(T (APPEND

(PREOBR (CAR L))

(PREOBR (CDR L))))))

Рекурсія є взаємної між двома і більше функціями, якщо вони викликають друг друга:

(DEFUN f ...

...(g ...) ...)

(DEFUN g ...

...(f ...) ...)

Наприклад напишемо функцію звернення чи дзеркального відображення в вигляді двох взаємно рекурсивних функцій наступним образом:

(DEFUN obr (l)

(COND ((ATOM l) l)

(T (per l nil))))

(DEFUN per (l res)

(COND ((NULL l) res)

(T (per (CDR l)

(CONS (obr (CAR l)) res)))))

9. Функція як дані.

Якщо ви вже знайомі з якоюсь алголоподібною мовою типу Сі або Перла, то поняття функції в Ліспі може здатися дивним, але воно значно ближче до математичного явищу функції, ніж «функції» Сі, які вірніше було б назвати більш процедурними. Функція в Ліспі є однозначне відображення множини початкових даних на множину її значень. У функції може бути довільно багато аргументів, - від нуля до будь-якого кінцевого числа, - але обов'язково має бути хоча б одне значення. Звичайна для багатьох мов префіксная запис виклику функцій:

plus(2,2)

трохи видозмінюється в силу відсутності синтаксису: ідентифікатор ("ім'я" функції, спецоператора або макровиклику) стає головним елементом списку:

(plus 2 2)

У чистому функціональному Ліспі (що є строгою підмножиною будь-якого з діалектів) функції не повинні володіти побучним ефектом, тобто змінювати значення змінних з нелокальним станом. В іншому випадку, при декількох викликах однієї функції можуть бути отримані різні значення, що заплутує будь-яку складну програмну систему, і чого прагне уникнути функціональне програмування.

Визначення функції засноване на лямбда-численні. Вихідний варіант запису лямбда-вирази, запропонований його автором Черчем, виглядає як . Лісп-запис виглядає так: (lambda (x1 x2 ...) fn). x1, x2 ... - Вільні параметри, і, так, можуть бути безболісно замінені на інші без зміни значення лямбда-вирази.

Наприклад, суму квадратів на мові лямбда-виразів можна визначити як (lambda (xy) (+ (* xx) (* yy))). Виклик лямбда-функції з конкретними значеннями ((lambda (xy) (+ (* xx) (* yy))) 2 3) => (+ (* 2 2) (* 3 3)) => 13 може бути зроблений раз , при бажанні викликати таку функцію ще раз необхідно повторити запис ще раз. Це незручно. Було б зручніше написати щось типу (сума-квадратів 2 3). Це можливо, якщо ви попередньо визначте функцію сума-квадратів

(defun сумма-квадратов (x y)

((lambda (a b) (+ (* a a) (* b b)))

x y))

… але не лякайтеся: для зручності цей запис може бути скорочений до

(defun сумма-квадратов (x y)

(+ (* x x) (* y y)))

((lambda (x)

Одна з переваг таких незвичних особливостей мови Лісп в тому, що ви можете написати щось типу

((lambda (y)

(list x y))

'second))

'first)

Тут лямбда-вираз є тілом іншої лямбда-виразу. Воно так само може бути одним з його аргументів.

Подивитися раніше заданий визначення функції можна за допомогою функції symbol-function:

>> (symbol-function 'сумма-квадратов)

<< #<function сумма-квадратов (x y) (declare (system::in-defun сумма-квадратов))

(block сумма-квадратов (+ (* x x) (* y y)))>

Штрих перед назвою функції поставлений невипадково. В Ліспі будь-яке ім'я відповідає деякому символу. Ім'я функції - не виняток, тобто значення символу «сума-квадратів» - не визначення функції; воно невизначене, поки не буде присвоєно вручну (наприклад, через setq.) Штрих - це скорочений запис спецоператора quote, що повідомляє інтерпретатору, що потрібно вивести ім'я даного символу без обчислення його значення.

>> (symbol-function сумма-квадратов) ; это выдаст ошибку: переменной сумма-квадратов не присвоено значение

>> (symbol-function (quote сумма-квадратов)) ; это правильно, но долго

>> (symbol-function 'сумма-квадратов) ; так лучше всего

Якщо всі аргументи лямбда-вирази не повинні обчислюватися, то зручно використовувати nlambda замість lambda.

quote'е протиставлений eval, примусово обчислює значення виразу.

>> (quote сумма-квадратов) => сумма-квадратов

>> (eval (quote сумма-квадратов)) => ошибка!

>> (quote (eval (quote сумма-квадратов))) => (eval 'sum)

В останньому випадку квотується не тільки сума-квадратів, але і сам eval!