Добавил:
Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:

книги / Функциональное программирование

..pdf
Скачиваний:
0
Добавлен:
12.11.2023
Размер:
14.84 Mб
Скачать

Например,

(defun fn (x &optional (y (+ x 2))) (list x y)),

где для функции fn есть обязательный параметр x и необязательный y со значением по умолчанию (+ X 2). Теперь, если обратиться с запросом:

>(fn 2) ; вычисляется значение по умолчанию Y = X + 2 (2 4)

>(fn 2 5)

(2 5)

Естественно, ключевые слова можно использовать и в λ-выра- жениях:

> ((lambda (x &optional (у (+ x 2))) (list x у)) 2 5) (2 5)

Рассмотрим теперь ключевое слово &REST, которое связывает со списком параметров, указанных в вызове, т.е. можно задавать переменное количество параметров.

Например,

(defun fn (x &optional y &rest z) list (x y z)) FN

> (fn ‘a) (A NIL NIL)

> (fn ‘a ‘b ‘c ‘d) (A B (C D))

Ключевое слово &REST связывает значения последних аргументов по причине того, что у них отсутствует функция блокировки вычислений (см. quoteили «’»).

Фактические параметры, соответствующие формальным параметрам, обозначенным ключевым словом &KEY, можно задавать в вызове при помощи символьных ключей. Ключом является имя формального параметра, перед которым поставлено двоеточие, например «:Х». Соответствующий фактический параметр будет следовать в вызове функции за ключом и отделяться от него пробелом. Достоинством ключевых параметров является то, что их можно перечислять в вызове, не зная их по-

21

рядок в определении функции или λ-выражении. Например, у следующей функции параметры x, y и z являются ключевыми:

(defun fn (&key x y (z 3)) list (x y z)) FN

> (fn :y 2 :x 1) (1 2 3)

Параметр z можно было не задавать, так как ключевые параметры являются необязательными.

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

Ранее мы использовали функции, возвращающие одно значение, или один лисповский объект. Во многих лисп-системах, в том числе и в «Коммон Лиспе», можно определить и многозначные функции (multiple valued functions), которые возвращают множество значений. Этот механизм более удобен, чем возврат значений через глобальную переменную или через построение списка результатов. Для выдачи и принятия многокомпонентных значений используются специальные формы.

1.8. Теория рекурсивных функций

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

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

22

функциям или самой определенной функции, но с более «простыми» аргументами. Вычисление этой функции заканчивается в тот момент, когда она сводится к неким граничным значениям.

Для справки. Рекуррентная формула – формула вида an = f (n, an – 1, an – 2, …, an – p), n ≥ p + 1, выражающая каждый член последовательности an (n N) через P предыдущих членов. Общая проблематика рекуррентных вычислений является предметом теории рекурсивных функций. Примеры использования рекуррентных формул: вычисление факториала натурального числа, чисел Фибоначчи, некоторых интегралов, решение дифференциального уравнения Бесселя, нахождение длины стороны при удвоении числа сторон правильного вписанного многоугольника и мн. др.

Например, классическая задача нахождения какого-то члена ряда Фибоначчи 0, 1, 1, 2, 3, 5, … используя префиксную нотацию «Лиспа», можно определить с помощью следующих рекуррентных формул:

(fib 0) = 0, (fib 1) = 1,

(fib n) = (+ (fib (– n 1)) (fib (– n 2))).

Существуют также функции, не относящиеся к задачам с простой рекурсией. В качестве примера можно привести функцию Аккермана, задающуюся рекуррентными формулами:

(Ack 0 х у) = (+ у х), (Ack 1 х у) = (* у х), (Ack 2 х у) = (^ y х),

(Ack (+ z 1) x у) = (lambda (и v) (Ack z и v)).

Функция Аск не является примитивно рекурсивной, хотя она и считается вычислимой, т.е. ее можно определить и вычислить ее значение за конечное время.

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

23

нечным. Например, вычисление числа π (пи) в надежде, что искомая последовательность обнаружится, но определить, закончится ли когда-нибудь вычисление, невозможно. Такие функции называются общерекурсивными.

До изучения языка трудно говорить о его особенностях, но тем не менее…

Представление программы и данных производится одинаково – через списки, что позволяет самой программе видоизменять содержимое не только других программ, но и саму себя.

«Лисп» относится к классу безтиповых языков программирования, т.е. не требуется резервировать память под конкретные объекты.

«Лисп» имеет необычный синтаксис. Из-за большего числа скобок Lisp расшифровывают как Lots of Idiotic Silly Parentheses (масса глупых круглых скобок). Программы, написанные на «Лиспе», во много раз короче, чем написанные на традиционных языках программирования.

1.9. Выражения и списки

Основу «Лиспа» составляют символьные выражения (S-вы- ражения, Simbolic expression), которые являются основной структурой данных.

Примеры S-выражений:

(ИВАН ПЕТРОВ ПЕРМЬ 18ЛЕТ), ((ПЕТР 19) (ОЛЯ 20) (МИХРЮТКА 21)).

S-выражение – этолибоатом,либосписок.Символьныйатом, или символ, – это не идентификатор переменной в обычном языке программирования. Символ, как правило, обозначает какой-либо предмет,объектвещь,действие.Атом–простейшее S-выражение.

Список – это основной тип данных в «Лиспе», заключается в круглые скобки, элементы списка разделяются пробелами. Пустой список обозначается парой скобок – (). Для обозначения пустого списка используется также специальная константа nil.

24

Примеры списков:

;пятиэлементный список – (1 2 3 4 5)

;четырехэлементный список – (1 2 ((3) 4) 5)

;одноэлементный список – ((1 2 3 4 5))

Первый элемент списка называется головой списка, все прочие элементы, кроме первого, представленные как список, называются хвостом списка.

Примеры разделения списка на голову и хвост:

Список

Голова

Хвост

(1

2 3 4

5)

1

(2 3 4 5)

(1 2 ((3)

4) 5)

1

(2 ((3) 4) 5)

((1

2 3 4

5))

(1 2 3 4 5)

()

 

(1)

 

 

1

()

1.10.Основные функции

В«Лиспе» для вызова функции принята префиксная форма записи, при которой как имя функции, так и ее аргументы записываются в виде элементов списка, причем имя функции – это всегда первый элемент списка.

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

Например:

(+ 3 5)

; возвращаемое значение 8

(* (+ 1 2) (+ 3 4)) ; возвращаемое значение 21

(sin 3.14)

; возвращаемое значение 0.00159265

Список может рассматриваться и не как вызов функции, а как перечень равноправных элементов. Для блокирования вызова функции существует стандартная функция quote или «’».

25

Например, список

(+ 3 5)

будет восприниматься как вызов функции суммирования с аргументами 3 и 5. Если же использовать данный список в качестве аргумента функции

(quote (+ 3 5)),

то список воспринимается именно как список. То есть применение функции quote блокирует вызов функции, и ее имя воспринимается как обычный элемент списка. Или, иначе говоря, если список является аргументом функции quote, то первый элемент списка не считается именем функции, а все прочие элементы не считаются аргументами функции.

Для функции quote существует сокращенная форма записи, вместо имени функции quote используется апостроф перед открывающейся скобкой списка. Например:

‘(+ 3 5)

Еще примеры:

>(+ 3 5))

8

>(quote (+ 3 5)) (+ 3 5)

>‘(+ 3 5)

(+ 3 5)

> (quote (quote (+ 3 5))) (QUOTE (+ 3 5))

Здесь и везде далее после символа «>» следует запрос к лисп-системе в режиме диалога. Ниже приводится результат, выданный «Лиспом».

Для выполнения операции присваивания используется функция SET:

(SET variable value).

26

Причем, если не требуется вычисления аргументов, их нужно предварить апострофами. Например,

>(set ‘x 5)

5

>x

5

>(set ‘y (+ 6 12)) 18

>y

18

>(set 'a 'b)

B

>(set a 'c)

C

>a

B

>b

C

>c

error: unbound variable – C

Аналогично функции SET работает функция SETQ (SET QUOTE), но с одной особенностью: при ее использовании не нужно ставить функцию QUOTE. Для первого аргумента блокировка вычисления выполняется автоматически.

>(setq x 5)

5

>x

5

>(setq y (+ 6 12)) 18

>y

18

Для определения пользовательской функции можно воспользоваться стандартной функцией DEFUN (Define FUNction):

27

(DEFUN name (fp1 fp2 … fpN) (form1 form1 … formN)),

где name – это имя новой пользовательской функции, (fp1 fp2 … fpN) – список аргументов или формальных параметров, а (form1 form1 … formN) – тело функции, т.е. последовательность действий, выполняемых при вызове функции.

Пример – функция для вычисления суммы квадратов:

(DEFUN squaresum (x y) (+ (* x x) (* y y)))

Результат работы: >(squaresum 3 4) 25

>(squaresum -2 -4) 20

Еще один пример: функция DEFTYPE, определяющая тип выражения (пустой список, атом или список).

(defun deftype(arg) (cond

((null arg) ‘emptylist) ((atom arg) ‘atom) (t ‘list)

)

)

Результат работы:

>(deftype ()) EMPTYLIST

>(deftype 'abc) ATOM

>(deftype '(a b c)) LIST

28

1.11.Инфиксная и префиксная нотация

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

x + y, x – y,

x * (x + z).

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

f (x), g (x, y),

h (x, g (y, z)).

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

(f x), (g x y),

(h x (g y z)), (+ x y),

(– x y),

(* x (+ x z)).

Достоинства префиксной нотации:

упрощается анализ выражений, так как первый элемент является идентификатором на структуру;

данные и программа записываются однообразно, а значит, появляется возможность применения элементов искусственного интеллекта.

Традиционное программирование задает порядок вычис-

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

29

лении переменных, их обработке и выдаче на основе базовых операторов до тех пор, пока желаемое конечное значение не будет получено. Затем его помещаем в нужное место.

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

всвою очередь, вызывает другие необходимые функции, и т.д.

всоответствии с иерархией определений и структурой условных предложений. Функции также часто вызывают сами себя прямо или опосредованно.

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

«Чистое» функциональное программирование не признает присваиваний, условий, циклов и т.д. Циклические вычисления выполняются с использованием рекурсии, которая является основным средством функционального программирования.

1.12.Базовые функции

В«Лиспе» существует пять основных функций, называемых базовыми:

1) (carlist)–отделяетголову списка(первыйэлементсписка); 2) (cdr list) – отделяет хвост списка (все элементы, кроме

первого, представленные в виде списка);

3) (cons head tail) – соединяет элемент и список в новый список, где присоединенный элемент становится головой нового списка;

4) (equal object1 object2) – проверяет объекты на равенство; 5) (atom object) – проверяет, является ли объект атомом. Так как функции EQUAL и ATOM возвращают значения

nil или t, их можно назвать базовыми предикатами.

30