Продолжается подписка на наши издания! Вы не забыли подписаться?

Искусство метапрограммирования

Автор: Джонатан Бартлет
Материал предоставил: www.ibm.com/developerworks/ru
Опубликовано: 18.04.2007

Генерирующие код программы часто называют метапрограммами; написание этих программ называется метапрограммированием. Создание программ, генерирующих код, имеет многочисленные применения.

Данная статья объясняет, почему желательно знать метапрограммирование, и рассматривает некоторые компоненты этого искусства. Мы детально рассмотрим текстовые макроязыки, исследуем специализированные генераторы кода и обсудим, как их создать, проанализируем расширенные возможности макропрограммирования с использованием Scheme.

Часть 1. Различные применения метапрограммирования

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

Во-вторых, если у вас есть большое приложение, в котором множество функций включает длинный стереотипный код, вы можете создать мини-язык, который будет создавать стереотипный код вместо вас, и даст вам возможность кодировать только важные части программы. Здесь, если получится, лучше всего абстрагировать стереотипные фрагменты в функцию. Но часто это не так просто. Возможно, есть список переменных, которые нужно объявить в каждом экземпляре, возможно, есть необходимость зарегистрировать обработчики ошибок, возможно, существует несколько стереотипных фрагментов, которые должны включать код в определенных обстоятельствах. Все это делает создание простой функции невозможным. Часто в таких ситуациях хорошей идеей является создание мини-языка, что позволит вам работать с таким кодом более простым способом. Этот мини-язык затем конвертируется в исходный код на обычном языке программирования перед компиляцией.

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

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

Основные текстовые макроязыки

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

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

Препроцессор C

Сначала давайте посмотрим на метапрограммирование, в котором используются текстовые макроязыки. Текстовым макросом является макрос, который непосредственно влияет на текст, написанный на языке программирования, и при этом не знает языка или не имеет отношения к его смыслу. Наиболее широко используемыми текстовыми макросистемами являются препроцессор C и макропроцессор M4.

Если вы работали с языком C, то, возможно, имели дело с макросом #define. Текстовые макрорасширения – хотя и не идеальный, но простой способ выполнить метапрограммирование на начальном уровне во многих языках, которые не имеют более развитых возможностей генерирования кода. В листинге 1 приведен пример макроса #define:

Листинг 1. Простой макрос для перестановки двух значений
#define SWAP(a, b, type) { type __tmp_c; c = b; b = a; a = c; }

Этот макрос дает возможность переставить два значения данного типа. Эту операцию лучше всего делать в макросе по нескольким причинам:

В листинге 2 приведен пример использования макроса:

Листинг 2. Использование макроса SWAP.
#define SWAP(a, b, type) { type __tmp_c; c = b; b = a; a = c; }
int main()
{
    int a = 3;
    int b = 5;
    printf("a is %d and b is %d\n", a, b);
    SWAP(a, b, int);
    printf("a is now %d and b is now %d\n", a, b);

    return 0;
}

Во время работы C-препроцессор текст изменяется с:

SWAP(a, b, int)

на:

{ int __tmp_c; __tmp_c = b; b = a; a = __tmp_c; }.

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

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

Листинг 3. Макрос, возвращающий меньшее из двух значений
#define MIN(x, y) ((x) > (y) ? (y) : (x))

Возможно, вы удивитесь, почему используется так много скобок. Причина в старшинстве операторов. Например, если написать MIN(27, b=32), без этих скобок макрос раскроется в 27 > b = 32 ? b = 32 : 27, что приведет к ошибке компиляции, поскольку выражение 27 > b выполнится раньше из-за старшинства операций. Если опять поставить скобки, то все будет работать так, как ожидалось.

К сожалению, есть еще и вторая проблема. Любая функция, вызванная как параметр, будет вызываться каждый раз, когда она появляется с правой стороны. Помните, препроцессор C не знает ничего о языке C и только выполняет текстовую подстановку. Следовательно, если вы выполните макрос MIN(do_long_calc(), do_long_calc2()), то он раскроется в ( (do_long_calc()) > (do_long_calc2()) ? (do_long_calc2()) : (do_long_calc())). Его выполнение займет длительное время, поскольку как минимум одно вычисление будет выполнено дважды.

Еще хуже, когда одно из этих вычислений имеет побочный эффект (например, распечатка, изменение глобальной переменной и т.д.), поскольку этот побочный эффект выполнится дважды. Эта проблема "множественных вызовов" может даже повлиять на возврат из макроса ошибочного значения, если одна из функций возвращает различное значение в каждом вызове.

Более подробная информация по макропрограммированию препроцессора C доступна в руководстве по C-препроцессору GNU (см. [6]).

<...>

Заключение

Метапрограммирование широко используется в широкомасштабных программных проектах. В данной статье я коснулся инструментальных средств, необходимых для метапрограммирования на языке Scheme, а также привел несколько примеров. Технология метапрограммирования применялась в нескольких прикладных областях:

В Scheme вы можете использовать возможности макроязыка для определения практически любого типа предметно-ориентированного языка. Средство для этого существует. Просто нужно решить, какие возможности реализуются более легко и более понятно при помощи макрорасширений, а какие – при помощи обычного кода.

Ресурсы

Основной публикацией, описывающей syntax-case является статья Кента Дибвига (Kent Dybvig) "Написание гигиенических макросов в Scheme с использованием Syntax-Case" – http://www.cs.indiana.edu/~dyb/pubs/tr356.pdf.

Дибвиг далее расширяет это описание в "Главе 8. Синтаксическое расширение языка программирования Scheme" – http://www.scheme.com/tspl3/syntax.html#./syntax:h0.

........................
"С полным содержанием данной статьи можно ознакомиться в печатной версии журнала"

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

Copyright © 1994-2016 ООО "К-Пресс"