DTL. Часть первая

Mar 09, 2010 22:04

DTL
Введение

Итак, о чём же пойдёт речь в этот раз?

DTL. Data Transform Language. Язык (описания) преобразований данных. Для его создания мною были использованы принципы Пролога, XSLT, конечных грамматик и деревьев выражений.

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

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

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

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

В реальной жизни нас интересует прежде всего результат, а не способ его достижения. Если мы хотим поесть колбасы, мы говорим кому-нибудь: «Сходи купи колбасу». При этом мы не объясняем, что именно нужно сделать для этого - одеться, взять деньги, выйти на улицу, сходить в магазин, совершить сложную процедуру покупки, вернуться и отдать колбасу. Исполнитель сам прекрасно понимает, что он должен сделать.

Если же мы имеем дело с менее интеллектуально развитым сожителем, ему придётся объяснять в подробностях, что ему нужно одеть, в какой магазин зайти и какой вид колбасы приобрести. Дополнительные сложности и головная боль, не правда ли?

Так вот, первый подход, описывающий только желаемый результат, - суть декларативный. Второй, описывающий последовательность действий (пусть и не до уровня «передвинь левую ногу, потом правую») - императивный. Первый, естественно удобнее для руководителя. Но! Он требует более продвинутого исполнителя и больших ресурсов - последний должен сначала обдумать поставленную перед ним задачу и решить, каким же образом ему нужно достичь цели.

Так вот, руководствуясь именно удобством разработки (ведь данную технологию я собрался использовать в нескольких проектах), я и решил разработать язык декларативного стиля. Ничего оригинального в нём нет, просто в него включены наиболее удачные (имхо) элементы из других языков. Потребность в таком языке у меня имелась, ибо хотелось по-минимуму писать однообразный код.

Хорошо, потребность есть, но зачем создавать язык, скажете вы? Наверняка в том же Perl, F# или Ruby уже есть возможности выразить многое из того, что нужно.

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

Основными требованиями к языку были следующие:
1)                      понятность текстов без каких-либо преобразований. Выражения на языке должны легко читаться (NB: Ну, может, насчёт «легко» я погорячился, но читать выражения возможно);
2)                      схожесть с синтаксисом, использованным мной в дипломной работе (чтобы не сильно переделывать уже накопленные данные по правилам преобразования для русского языка);

3)                      простота интерпретации (соответственно разработанным интерпретатором). Тоже немаловажно, ибо мне было лень реализовывать что-то уж слишком капитальное :) Следы данного упрощения вы ещё увидите.

Итак, давайте перейдём к делу. Что описывает язык? Преобразования. Значит, есть входные данные и есть выходные. Существует ли язык, который уже предназначен для аналогичной работы? Конечно! Это язык XSLT.

Вся прелесть XSLT заключается в его интуитивно понятной идеологии. Есть шаблоны, которые мы можем встретить во входном документе. Встретили шаблон - посмотрели, что для данного шаблона нужно вывести в результат. Увидели адрес - написали «Адрес:» и вывели его. Простая логика, понятная каждому.

Посему DTL работает по тому же принципу, но, в отличие от XSLT, принимает на вход не XML-документы, а нечто другое.

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

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

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

Таким образом, мы, подобно XSLT, можем ввести простое и понятное правило преобразования на основе шаблонов:

Шаблон => Результат

Слева - шаблон входных данных, справа - то, что нам нужно получить. Например:

Горит зелёный цвет => Можно переходить улицу

Или так:

Можно переходить улицу <= Горит зелёный цвет.

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

Если обратиться к языку программирования Пролог, мы увидим, что условия очень удобно записывать в виде предикатов - утверждений, который могут быть либо истинными, либо ложными. Использование логики предикатов позволяет манипулировать полноценными математическими моделями преобразуемого предмета, совершать логические выводы и, тем самым, использовать весьма сложные шаблоны. Таким образом,

Шаблон - это предикат, проверяемый в текущей позиции входных данных.

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

Вот простой пример преобразователя, заменяющего все вхождения символов ab на ba и ac на c во входном тексте:

ab => ba

ac => c

Просто? Просто. Легко читать? Легко. Этого мы и добивались.

Как запустить свой первый пример? Создаём проект в Visual Studio, подключаем к нему волшебную библиотеку Lingware.dll (можно легко достать, так как она входит в SIQuester 3.4) и используем преобразователь из неё:

var tree = ExpressionTree.Parse(“ab => ba\nac => c”);
var result = tree.TransformAll(“xcccacbab”).ToString(); // получаем xccccbba

Ничего сложного.

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

Пока все просто? Пойдём далее.

Игры будущего

Previous post Next post
Up