SoftCraft
разноликое программирование

Отправная точка
Программирование
Windows API
Автоматы
Нейроинформатика
Парадигмы
Параллелизм
Проектирование
Теория
Техника кодирования
Трансляторы
Прочие вопросы

Разное

Беллетристика
Брюзжалки
Цели и задачи
Об авторе

Сервис

Форум
Подписка на новости


Процедурно-параметрическое программирование


[ Содержание | 1 | 2 | 3 | 4 | 5 | >>> | Источники ]


Текст статьи можно загрузить из архива (53 кб)

Процедурно-параметрическое программирование

© 2001 А.И. Легалов

Посвящается методу

вычисления действительных
корней квадратного уравнения

ax2+bx+c=0

Резюме

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

Параметрическая обработка альтернатив

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

Вместе с тем, кроме ранее рассмотренных способов, существует еще один метод вычисления F(D), который можно использовать в процедурном программировании. Установить функциональную зависимость между обработчиком специализации fi и типом специализации tj можно не только алгоритмически, но и таблично. Такая однозначная зависимость, назовем ее параметрической, может быть показана не фрагментом кода, а таблицей, реализация которой может быть выполнена с использованием двумерного массива:

Значения
функции F(D)

Значения типа данных T

t1

t2

tn

F1(D)

f11(D1)

f 12(D2)

f 1n(Dn)

F 2(D)

f 21(D1)

f 22(D2)

f 2n(Dn)

F m(D)

f m1(D1)

f m2(D2)

f mn(Dn)

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

Моделирование параметрического подхода

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

Конструирование параметрического обобщения

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

Вид фигуры

Обозначение типа
данных

Значение перечислимого типа

Прямоугольник

rectangle

RECTANGLE

Треугольник

triangle

TRIANGLE

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

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

struct shape {
    // значения локальных ключей для каждой из фигур
    enum key {RECTANGLE=0, TRIANGLE=1};
    key k; // ключ
    // используется универсальный указатель
    void *s; // подключается любая специализация
};

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

Создание экземпляров параметрического обобщения

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

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

rectangle *Create_rectangle(int x, int y);
triangle  *Create_triangle(int a, int b, int c);

//------------------------------------------------------------------
// Динамическое создание обобщенного прямоугольника
shape *Create_shape_rectangle(int x, int y)
{
    shape *s = new shape;
    s->k = shape::key::RECTANGLE;
    s->s = (void*)Create_rectangle(x, y);
    return s;
}

//------------------------------------------------------------------
// Динамическое создание обобщенного треугольника

shape *Create_shape_triangle(int a, int b, int c)
{
    shape *s = new shape;
    s->k = shape::key::TRIANGLE;
    s->s = (void*)Create_triangle(a, b, c);
    return s;
}

//------------------------------------------------------------------
// Инициализация обобщенного прямоугольника

void Init_rectangle(shape &s, int x, int y)
{
    s.k = shape::key::RECTANGLE;
    // Необходимо создать прямоугольник, так как
    // он формируется динамичиески.
    s.s = (void*)Create_rectangle(x, y);
}

//------------------------------------------------------------------
// Инициализация обобщенного треугольника

void Init_triangle(shape &s, int a, int b, int c)
{
    s.k = shape::key::TRIANGLE;
    // Необходимо создать треугольник, так как
    // он формируется динамичиески.
    s.s = (void*)Create_triangle(a, b, c);
}

//------------------------------------------------------------------

Следует отметить использование процедур, ранее написанных для создания отдельных специализаций.

Построение обработчиков параметрических обобщений

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

Значения
функции

Значения признака специализации
(задается через перечислимый тип)

RECTANGLE

TRIANGLE

Clear

Delete_rectangle

Delete_triangle

Out

Out_rectangle

Out_triangle

Area

Area_of_rectangle

Area_of_triangle

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

//------------------------------------------------------------------

// Обработчик, предназначенный для удаления прямоугольника.
// Используется как элемент параметрического массива в процедуре
// очистки обобщенной фигуры.
void Delete_rectangle(shape &s)
{
    delete (rectangle*)s.s;
}

//------------------------------------------------------------------

// Обработчик специализации, предназначенный для удаления треугольника.
// Используется как элемент параметрического массива в процедурах
// очистки обобщенной фигуры.
void Delete_triangle(shape &s)
{
    delete (triangle*)s.s;
}

//------------------------------------------------------------------

// Очистка обобщенной фигуры удалением специализации.
// Остается только "голова" обобщения, которая может
// повторно инициализироваться любым значением.
// Используется параметрический массив.указателей на процедуры
// удаления специализаций.
void (*Clear_shape[])(shape &s) =
    {&Delete_rectangle, &Delete_triangle};

//------------------------------------------------------------------
// Дополнительное переопределение процедуры очистки фигуры,
// сделанное для сокрытия ее реального вида.
// Может отсутствовать.
void Clear(shape &s)
{
    Clear_shape[s.k](s);
}

//------------------------------------------------------------------

Сформированная в конце процедура очистки void Clear(shape &s) показывает, каким образом можно инкапсулировать доступ к параметрическому массиву. Тем самым скрывается один из недостатков моделирования параметрического обобщения: дважды в списке параметров осуществляется обращение к одному и тому же объекту.

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

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

void Out(rectangle &r);
void Out(triangle &t);

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

void Out_rectangle(shape &s)
{
    Out(*((rectangle*)s.s));
}

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

void Out_triangle(shape &s)
{
    Out(*((triangle*)s.s));
}

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

void (*Out_shape[])(shape &s) =
    {&Out_rectangle, &Out_triangle};


//------------------------------------------------------------------
// Дополнительное переопределение процедуры вычисления площади
// обобщенной фигуры, сделанное для сокрытия ее реального вида.
// Может отсутствовать.

void Out(shape &s)
{
    Out_shape[s.k](s);
}

//------------------------------------------------------------------

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

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

double Area(rectangle &r);
double Area(triangle &t);

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

double Area_of_rectangle(shape &s)
{
    return Area(*((rectangle*)s.s));
}

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

double Area_of_triangle(shape &s)
{
    return Area(*((triangle*)s.s));
}

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

double (*Area_of_shape[])(shape &s) =
    {&Area_of_rectangle, &Area_of_triangle};


//------------------------------------------------------------------
// Дополнительное переопределение процедуры вычисления площади
// обобщенной фигуры, сделанное для сокрытия ее реального вида.
// Может отсутствовать.

double Area(shape &s)
{
    return Area_of_shape[s.k](s);
}

//------------------------------------------------------------------

Исходный текст программы, моделирующей параметрическое обобщение, приведен в архиве pp_examp1p.zip.

Да ведь это уже было!

Приведенный метод обработки альтернатив не является моим величайшим открытием. Он издавна используется в практике процедурного программирования и даже намного чаще, чем имитация объектно-ориентированного подхода, так как менее громоздок при тех же результатах. У Страуструпа [Страуструп] он описан при демонстрации указателей на функции. Ясно также, что непосредственное использование таких приемов не повысит шансов процедурного программирования в борьбе с ОО стилем. Ведь поиск и обнаружение ошибок в программе необходимо осуществлять во время ее выполнения. Однако приведенный пример указывает путь по дальнейшему языковому расширению процедурного подхода. И здесь я готов отстаивать свое первенство на предлагаемые решения.

Параметрический полиморфизм

Посмотрев еще раз на рисунок, демонстрирующий особенности динамического формирования альтернатив в объектном обобщении (рис. 1), мы увидим, что множество процедур, осуществляющих обработку некоторой специализации, размещаются в единой таблице виртуальных функций. Из рисунка также видно, что одна и та же обобщающая процедура Fi "размазана" по различным таблицам, но, при этом, в каждой из них находится в одной и той же позиции.

Теперь посмотрим на то, как организованы структуры данных, используемые при формировании параметрического обобщения (рис. 2). Все правильно! Перпендикулярно, точнее, ортогонально! Так всегда и происходит при сопоставлении процедурного подхода с объектным! Специализации, реализующие одну обобщающую процедуру, оказались собраны в едином массиве. При этом отдельные специализированные процедуры различного назначения, используемые для обработки одной и той же специализации, находятся в разных массивах, но на одной и той же позиции. То есть, отличие только в том, что иначе разрезали матрицу! А раз так, то почему бы ни подыскать такие языковые надстройки над процедурным подходом, которые обеспечивают необходимый синтаксический и семантический контроль ортогонального разрезания на этапе компиляции. Ведь нашли же, в свое время, аналогичную объектно-ориентированную надстройку, создав языки, методологии, религию. Хотя, я думаю, исходные посылки были другими.

По аналогии с ООП, можно говорить о параметрическом полиморфизме как методе, обеспечивающем изменение поведения процедур путем создания отношений между параметрическими обобщениями, объединяющими альтернативные специализации, и обобщающими процедурами, предоставляющими специализированные обработчики для соответствующих комбинаций специализированных данных. В представленной выше программе роль параметрического обобщения выполняет фигура shape. Роль обобщающих "процедур", обеспечивающих полиморфизм, возлагается на массивы указателей Clear_shape, Out_shape, Area_of_shape.

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

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


[ Содержание | 1 | 2 | 3 | 4 | 5 | >>> | Источники ]




Статьи