Главная » XSLT » Обобщенное и функциональное программирование – Введение

0

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

В этой главе ставятся более амбициозные цели. Рассматриваемые примеры будут применимы к широкому кругу задач, причем модифицировать предложен­ный базовый код вообще не потребуется. Читатели, знакомые с языком C++, а в особен­ности со стандартной библиотекой шаблонов (STL), знают, чего можно достичь с помо­щью обобщенного кода (обобщенных алгоритмов), который применим в различных контекстах. А имеющие опыт работы с функциональными языками программирования (например, List, ML, Haskell) знают, какой выразительной мощью обладают функции высшего порядка: функции общего назначения, которые специализируются путем пе­редачи других функций в качестве аргументов. В этой главе показано, что язык XSLT, хотя и не задумывался как язык для обобщенного или функционального програм­мирования, обладает возможностями и для того, и для другого.

Техника, применяемая в этой главе, – это работа на грани возможностей языка. Не всякий захочет пользоваться этими примерами на практике, не­которые из них сложны и работают медленно. Но я вспоминаю те дни, ког­да в C++ еще не было встроенной поддержки шаблонов. Можно было ими­тировать обобщенное программирование с помощью макросов, но это выглядело так неуклюже. Однако нашлось достаточно людей, распознав­ших заложенный потенциал, и вскоре шаблоны стали полноценной частью языка, быть может, даже одной из важнейших его характеристик, несмот­ря на распространение других объектно-ориентированных языков. По­добные эксперименты оказывают давление на язык и иногда заставляют его эволюционировать быстрее. И это хорошо, потому что языки, пере­ставшие развиваться, частенько отмирают.

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

Расширение содержимого глобальных переменных

В этой главе активно используется имеющаяся в XSLT возможность импорти­ровать (xsl:import) и переопределять в импортирующей таблице шаблоны, пе­ременные и другие элементы верхнего уровня.

Мне нравится употребляемый в объектно-ориентированных языках термин ® переопределение в применении к команде xsl:import. Однако технически правильно говорить, что некоторые элементы верхнего уровня в импор­тирующей таблице стилей имеют более высокий приоритет импорта, чем такие же элементы в импортируемой таблице. Подробное объяснение ра­боты каждого элемента верхнего уровня в XSLT в части, относящейся к xsl:import, можно найти в книге Майкла Кэя XSLT Programmer’s Reference (Wrox, 2001).

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

В следующей таблице определены две переменные. Первая – $data1- public-data – уникальна для данной таблицы, а вторая – $data – определена через первую, но ее можно переопределить:

<!– data1.xslt –>

<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:d="http://www.ora.com/XSLTCookbook/NS/data">

<xsl:output method="xml" indent="yes"/>

<d:data value="1"/> <d:data value="2"/> <d:data value="3"/>

<xsl:variable name="data1-public-data" select="document(”)/*/d:*"/> <xsl:variable name="data" select="$data1-public-data"/>

<xsl:template match="/"> <demo>

<xsl:copy-of select="$data"/>

</demo>

</xsl:template> </xsl:stylesheet>

Теперь создадим другую таблицу стилей, в которой значение $data будет расширено. В ней также определена уникальная переменная, которая объединяет открытые данные первой таблицы с локально определенными данными. Затем $data переопределяется в терминах объединения:

<!– data2.xslt –>

<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:d="http://www.ora.com/XSLTCookbook/NS/data">

<xsl:import href="data1.xslt"/>

<xsl:output method="xml" indent="yes"/>

<d:data value="5"/> <d:data value="7"/> <d:data value="11"/>

<xsl:variable name="data2-public-data"

select="document(”)/*/d:* | $data1-public-data"/>

<xsl:variable name="data" select="$data2-public-data"/>

</xsl:stylesheet>

Вот как выглядит результат работы data1.xslt:

<demo xmlns:d="data"> <d:data value="1"/> <d:data value="2"/> <d:data value="3"/> </demo>

А вот результат работы data2.xslt:

<demo xmlns:d="data">

<d:data       value="1"/>

<d:data       value="2"/>

<d:data       value="3"/>

<d:data       value="5"/>

<d:data       value="7"/>

<d:data       value="11"/> </demo>

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

Метки шаблонов

В XSLT нет прямого способа передать имя одного шаблона в другой, так что­бы второй шаблон мог косвенно вызвать первый. Другими словами, следующий код недопустим ни в XSLT 1.0, ни в XSLT 2.0:

<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">

<!— ТАКОЙ КОД В XSLT НЕДОПУСТИМ —>

<xsl:template match="/">

<!– Мы можем вызывать шаблоны по имени …–> <xsl:call-template name="sayIt">

<xsl:with-param name="aTempl" select=" ‘sayHello’ "/> </xsl:call-template>

<xsl:call-template name="sayIt">

<xsl:with-param name="aTempl" select=" ‘sayGoodbye’ "/> </xsl:call-template> </xsl:template>

<xsl:template name="sayIt">

<xsl:param name="aTempl"/>

<!–Ho имя не разрешается задавать в виде переменной –> <xsl:call-template name="{$aTemple}"/>

</xsl:template>

<xsl:template name="sayHello">

<xsl:value-of select=" ‘Hello!’ "/> </xsl:template>

<xsl:template name="sayGoodbye">

<xsl:value-of select=" ‘Goodbye!’ "/> </xsl:template>

</xsl:stylesheet>

Оказывается, что можно было бы писать очень полезный, повторно используе­мый код, если бы удалось достичь такого уровня косвенности, не выходя за рамки XSLT. К счастью, это возможно, только вместо вызова по имени нужно применить механизм сопоставления. Трюк состоит в том, чтобы определить шаблон, который может сопоставиться только с конкретным элементом данных. Этот элемент назы­вается меткой шаблона (template tag) и, по соглашению, метка располагается непос­редственно перед шаблоном, который помечает:

<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:f="http://www.ora.com/XSLTCookbook/namespaces/func">

<xsl:output method="text"/>

<xsl:template match="/">

<xsl:call-template name="sayIt">

<xsl:with-param name="aTempl" select=" ‘sayHello’ "/> </xsl:call-template> <xsl:call-template name="sayIt">

<xsl:with-param name="aTempl" select=" ‘sayGoodbye’ "/> </xsl:call-template> </xsl:template>

<xsl:template name="sayIt">

<xsl:param name="aTempl"/>

<!– Применить шаблоны, указав в атрибуте select элемент-метку, –> <!– уникальный для шаблона, который мы собираемся вызвать –> <xsl:apply-templates select="document(”)/*/f:func[@name=$aTempl]"/> </xsl:template>

<!– Помеченный шаблон состоит из элемента-метки и шаблона, –> <!– сопоставляемого с этим элементом –> <f:func name="sayHello"/>

<xsl:template match="f:func[@name=’sayHello’]">

<xsl:text>Hello!&#xa;</xsl:text> </xsl:template>

<!– Другой помеченный шаблон –> <f:func name="sayGoodbye"/>

<xsl:template match="f:func[@name=’sayGoodbye’]">

<xsl:text>Goodbye!&#xa;</xsl:text> </xsl:template>

</xsl:stylesheet>

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

Применяя этот прием, не забывайте включать «сторожевой» шаблон, с кото­рым будет производиться сопоставление, если никакой помеченный шаблон не подходит:

<xsl:template match="f:func">

<xsl:message terminate="yes"> BAD FUNC! Шаблон не соответствует объявлению generic:func. </xsl:message> </xsl:template>

Майкл Кэй упоминает еще один способ обобщенного программирования – когда шаблон сопоставляется сам с собой:

<xsl:template name="f:sayHello"

match="xsl:template[@name=’f:sayHello’]"> <xsl:text>Привет!&#xa;</xsl:text> </xsl:template>

<xsl:template name="f:sayGoodbye"

match="xsl:template[@name=’f:sayGoodbye’]"> <xsl:text>noKa!&#xa;</xsl:text> </xsl:template>

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

ОБОБЩЕННОЕ И ФУНКЦИОНАЛЬНОЕ ПРОГРАММИРОВАНИЕ

Обобщенное программирование – это способ организации повторно ис­пользуемых компонентов так, чтобы их можно было легко настроить почти или совсем без потери производительности. Для использования обобщенных ком­понентов (классов и функций) их необходимо инстанцировать, указав конкрет­ные типы и/или объекты.

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

Метод помеченных шаблонов можно интерпретировать двумя способами. С точки зрения обобщенного программирования, можно сказать, что sayIt – обобщенный шаблон, параметризованный другим шаблоном. А с точки зрения функционального программирования, sayIt – функция высшего порядка, принимающая другую функцию в качестве аргумента. Читатели, знакомые с обобщенным программированием в C++, наверное, скажут, что вторая интерпретация более точна, так как параметризация происходит на этапе выполнения, а не компиляции. Но я не считаю, что обобщенным программированием можно называть только то, что происходит на этапе компиляции. Позже мы увидим, что элементы-метки могут быть не просто заместителями имени функции; в них могут быть и данные, что напоминает характеристические классы (traits), применяемые при обобщенном программировании на C++.

См. также XSLT 1.0

Первым, кто придумал, как использовать обобщенное и функциональ­ное программирование в XSLT, был, насколько я знаю, Димитр Новачев (Dimitre Novatchev). Он написал несколько статей на эту тему. См. http:// fxsl.sourceforge.net. Рецепты обобщенного программирования, приведенные в этой главе, были написаны еще до того, как я познакомился с работой Нова- чева, и во многих отношениях наши подходы различаются. Я рекомендую знакомиться с работой Димитра только после того, как вы хорошо освоите мои примеры. Димитр заходит еще дальше, чем я, поэтому предлагаемые им методы сложнее. Он написал библиотеку FXSL для функционального про­граммирования на языке XSLT, ее можно скачать на странице http:// sourceforge.net/project/showfiles.php?group_id=53841.

XSLT 2.0

У Димитра Новачева есть также версия библиотеки FXSL, учитывающие новации, появившиеся в XSLT 2.0 (http://sourceforge.net/project/showfiles.php?group_id=53841). Самыми заметными из них являются функции и их перегрузка. Я настоятельно реко­мендую эту версию всем разработчикам на XSLT 2.0, которые интересуются про­граммированием «высшего порядка».

Ниже приводится несколько цитат из сообщения Димитра о библиотеке FSCL и функциональном программировании на XSLT. Здесь HOF означает Higher Order Function (функция высшего порядка).

Плюсы представляются очевидными:

1.              Возможность композиции: f:map может находиться в любом месте, где мог­ла бы быть цепочка функциональной композиции.

2.              Гораздо проще и понятнее написать

f:map(fsomeFun( ), $seq)

чем

<xsl:for-each select="$seq">

<xsl:sequence select="f:apply(fsomeFun( ), $seq)"/> </xsl:for-each>

Наличие HOF-обертки с тем же именем и сигнатурой, что у любой функ­ции (F) или оператора (O), дает по существу систему функционального программирования, в которой все стандартные функции и операторы XPath 2.0 (и все стандартные функции XSLT 2.0) становятся функциями высшего порядка.

Программист может сразу же начинать пользоваться всеми стандартными F & O XPath 2.0 (с которыми он хорошо знаком), ничего не делая дополнительно. На обучение времени практически не тратится. Весь богатый набор функ­ций уже реализован и гарантированно поддерживается любым XSLT 2.0-со- вместимым процессором.

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

Если придерживаться этих правил, то комбинация XSLT 2.0 + FXSL стано­вится по-настоящему замкнутой (относительно поддержки HOF) систе­мой функционального программирования.

И еще одно преимущество – все HOF-функции потенциально можно ис­пользовать и вне XSLT (например, если заранее откомпилировать их), на­пример, при встраивании XPath 2.0 в язык программирования и почему бы не в XQuery. Конечно, при таком подходе любая новая HOF должна быть сначала реализована на XSLT, а потом откомпилирована.

Подводя итог, можно сказать:

Система функционального программирования FXSL + XSLT 2.0 достигла такого состояния, при котором решения многих очень трудных задач мож­но выразить в виде однострочного выражения на языке XPath. Это может привести к радикальным изменениям в осмыслении того, как следует решать задачи на XSLT.

Далее Димитр демонстрирует, как можно выполнить сложные математи­ческие действия с помощью FXSL:

Другая причина заключается в том, что XSLT позволяет записывать мате­матические вычисления просто, компактно и элегантно. Например, чтобы получить последовательность:

NA10, N = 1 to 10

достаточно одной строки:

f:map(f:flip(f:pow( ),10), 1 to 10)

А вот как вычисляется сумма десятых степеней всех чисел от 1 до 10,

sum(f:map(f:flip(f:pow( ),10), 1 to 10))

И напоследок корень десятой степени из получившейся суммы:

f:pow(sum(f:map(f:flip(f:pow( ),10), 1 to 10)), 0.1)

Мангано Сэл  XSLT. Сборник рецептов. – М.: ДМК Пресс, СПБ.: БХВ-Петербург, 2008. – 864 с.: ил.

По теме:

  • Комментарии