Душкин Роман Викторович darkus@yandex ru Москва, 2001 лекция

Вид материалаЛекция

Содержание


Лекция 2. «Структуры данных и базисные операции»
Несколько слов о программной реализации
Рисунок 1. Представление пары в памяти компьютера
Рисунок 2. Графическое представление списочной структуры [a1, [a2, a3, [a4]], a5]
Подобный материал:
1   2   3   4   5   6   7   8   9   ...   19

Благодарности


Особую благодарность хочется выразить Сергиевскому Георгию Максимовичу, кото­рый в своё время обучил меня основам функционального программирования и помог с ор­ганизацией этого курса лекций.

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

Лекция 2. «Структуры данных и базисные операции»


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

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

Пусть заданы объекты некоторого первичного типа A. Сейчас совершенно не важно, что именно представляют собой эти выделенные объекты. Обычно считается, что на этих объ­ектах определён набор базисных операций и предикатов. По традиции, которая пошла ещё от МакКарти (автор Lisp’а), такие объекты называются атомами. В теории фак­ти­чес­кий способ реализации базисных операций и предикатов совершенно не важен, их су­щес­т­во­вание просто постулируется. Однако каждый конкретный функциональный язык ре­а­ли­зу­ет базисный набор по-своему.

В качестве базисных операций традиционно (и в первую очередь это объясняется те­о­ре­тической не­об­хо­димостью) выделяются следующие три:
  1. Операция создания пары — prefix (x, y)  x : y  [x | y]. Эта операция также называется кон­структором или составителем.
  2. Операция отсечения головы — head (x)  h (x). Это первая селективная операция.
  3. Операция отсечения хвоста — tail (x)  t (x). Это вторая селективная операция.

Селективные операции отсечения головы и хвоста также называют просто селекторами. Вы­деленные операции связаны друг с другом следующими тремя аксиомами:
  1. head (x : y) = x
  2. tail (x : y) = y
  3. prefix (head (x : y), tail (x : y)) = (x : y)

Всё множество объектов, которые можно сконструировать из объектов первичного ти­па в результате произвольного применения базисных операций, носит название множество S-выражений (обозначение — Sexpr (A)). Например:

a1 : (a2 : a3)  Sexpr

Для дальнейших исследований вводится фиксированный атом, который также при­над­ле­жит первичному типу A. Этот атом в дальнейшем будет называться «пустым списком» и обозначаться символами [] (хотя в разных языках функционального програмирования мо­гут существовать свои обозначения для пустого списка). Теперь можно определить то, чем собственно занимается функциональное программирование — собственное под­мно­жес­тво List (A)  Sexpr (A), которое называется «список над A».

Определение:

1. Пустой список []  List (A)

2. x  A & y  List (A)  x : y  List (A)

Главное свойство списка: x  List (A) & x  []  head (x)  A; tail (x)  List (A).

Для обозначения списка из n элементов можно употреблять множество различных но­та­ций, однако здесь будет использоваться только такая: [a1, a2, ..., an]. Применяя к такому спис­ку определенным образом операции head и tail можно добраться до любого элемента спис­ка, т.к.:

head ([a1, a2, ..., an]) = a1

tail ([a1, a2, ..., an]) = [a2, ..., an] (при n > 0).

Кроме списков вводится еще один тип данных, который носит название «списочная струк­тура над A» (обозначение — List_str (A)), при этом можно построить следющую струк­туру отношений: List (A)  List_str (A)  Sexpr (A). Определение списочной струк­ту­ры выглядит следующим образом:

Определение:

1. a  A  a  List_str (A)

2. List (List_str (A))  List_str (A)

Т.е. видно, что списочная структура — это список, элементами которого могут быть как атомы, так и другие списочные структуру, в том числе и обыкновенные списки. При­ме­ром списочной структуры, которая в тоже время не является простым списком, может слу­жить следующее выражение: [a1, [a2, a3, [a4]], a5]. Для списочных структур вводится та­кое понятие, как уровень вложенности.

Несколько слов о программной реализации


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

К
аждый объект занимает в памяти машины какое-то место. Однако атомы пред­с­тав­ля­ют собой указатели (адреса) на ячейки, в которых содержатся объекты. В этом случае пара z = x : y графически может быть представлена так, как показано на следующем рисунке.

Рисунок 1. Представление пары в памяти компьютера

Адрес ячейки, которая содержит указатели на x и y, и есть объект z. Как видно на ри­сун­ке, пара представлена двумя адресами — указатель на голову и указатель на хвост. Тра­диционно первый указатель (на рисунке выделен голубым цветом) называется a-поле, а второй указатель (на рисунке — зеленоватый) называется d-поле.

Для удобства представления объекты, на которые указывают a- и d-поля, в дальнейшем бу­дут записываться непосредственно в сами поля. Пустой список будет обозначаться пе­ре­черкнутым квадратом (указатель ни на что не указывает).

Т
аким образом, списочная структура, которая рассмотрена несколькими параграфами ра­нее ([a1, [a2, a3, [a4]], a5]) может быть представлена так, как показано на следующем ри­сун­ке:

Рисунок 2. Графическое представление списочной структуры [a1, [a2, a3, [a4]], a5]

На этом рисунке также хорошо проиллюстрировано понятие уровня вложенности — ато­мы a1 и a5 имеют уровень вложенности 1, атомы a2 и a3 — 2, а атом a4 — 3 со­от­вет­с­т­вен­но.

Остается отметить, что операция prefix требует расхода памяти, ибо при кон­с­т­ру­и­ро­ва­нии пары выделяется память под указатели. С другой стороны обе операции head и tail не тре­буют памяти, они просто возвращают адрес, который содержится соответственно в a- или d-поле.