92
Глава 2. Построение абстракций с помощ ью данных
где add и mul — не элементарные процедуры + и *, а более сложные устройства, кото-
рые проделывают соответству ющие операци и, какие бы типы данных м ы ни передавали
как аргументы a, b, x и y. Здесь важнейшая деталь состоит в том, что единственное,
что требуется знать процедуре linear-combination об a, b, x и y — это то, что про-
цедуры add и mul проделывают соответствующие действия. С точки зрения процедуры
linear-combination несущес твенно, что такое a, b, x и y, и еще менее существенно,
как они могут быть представлены через более простые данные. Этот же пример показыва-
ет, почему так важно, чтобы в нашем языке программир ования была возможность прямо
работать с составными объектами: иначе у процедур, подобных linear-combination,
не было бы способа передать аргументы в add и mul, не зная деталей их устройства
1
.
Мы начинаем эту главу с реализации описан ной выше системы арифметики рацио-
нальных чисел. Это послужит основанием для обсуждения составных данных и абстрак-
ции данных. Как и в случае с составными процедурами, основная мысль состоит в том,
что абстракция является методом ограничения сложности, и мы увидим, как абстракция
дан ных позволяет нам возводить полезные барьеры абстракции (abstraction barriers)
между разными частями программы.
Мы увидим, что главное в работе с составными данными — то, что язык програм-
мирования должен предоставлять нечто вроде «клея», так, чтобы объекты данных могли
соче таться, образуя более сложные объекты данных. Существует множество возможных
типов к лея. На самом деле мы обнаружим, что составные данные можно порождать во-
обще без использования каких-либо специальных операций, относящихся к «данным» —
только с помощью процедур. Это еще больше размоет границу между «процедурами» и
«данными», которая уже к концу главы 1 оказалась весьма тонкой. Мы также иссле-
дуем некоторые общеприня тые методы представления последовательностей и деревьев.
Важная идея в работе с составными данными — понятие замыкания (closure): клей для
соче тания объектов данных должен позволять нам склеивать не только элементарные
объекты данных, но и составные. Еще одна важная идея состоит в том, что составные
объекты данных могут служить стандартными интерфейсами (conventional interfaces),
так, чтобы модули программы могли сочетаться методом подстановки. Некоторые из этих
идей м ы продемонстрируем с помощью простого графического языка, использующего за-
мыкание.
Затем мы увеличим выразительную мощность нашего языка путем введения символь-
ных выражений (symbolic expressions) — данных, элементарные части которых могут
быть произвольными си м волами, а не только числами. Мы рассмотрим различные ва-
рианты представления множеств объектов. Мы обнаружим, что, подобно тому, как одна
и та же числовая функци я может вычисляться различными вычисл ительными пр оцес-
сами, существует множество способов представить некоторую структуру данных через
элементарные объекты, и выбор представления может су щественно влиять на запросы
манипулирующих этими данными процессов к памяти и ко времени. Мы исследуем эти
1
Способность прямо оперировать процедурами увеличивает выразительную силу нашего языка програм-
мирования подобным же образом. Например, в разделе 1.3.1 мы ввели процедуру sum, которая принимает в
качестве аргумента процедуру term и вычисляет сумму значений term на некотором заданном интервале.
Чтобы определить sum, нам необходимо иметь возможность говорить о процедуре типа term как о едином
целом, независимо от того, как она выражена через более простые операции. Вообще говоря, не имей мы
пон ятия «процедуры», вряд ли мы и думать могли бы о возможности определения такой операции, как sum.
Более того, пока мы размышляем о суммировании, детали того, как term может быть составлен из более
простых операций, несущественны.