|
The Functional Programming Language XSLT - A proof through examples
As a starting point, let's define some basic terminology and
present examples of the use of higher-order functions in FP. Then,
we'll take a first step towards our FP implementation in XSLT by
defining the "template reference" data type and demonstrate a
simple example of its use.
Imperative programming
The imperative style of programming describes a system as
evolving from an initial state through a series of state changes to
a set of desired final states. A program consists of commands that
change the state of the system. For example:
y = y + 3
will bring the system into a new state, in which the variable y
has a new value, which has been obtained by adding 3 to the value of y in
the previous state of the system.
Declarative programming
The declarative programming style specifies relationships
between different variables, e.g. the equation
z = y + 3
declares z to have a value of three more than the value
of y. Variables, once declared, cannot change their value.
Typically, there is no concept of state, order of
execution, memory,... etc.
In XSLT, the declarative approach is used: e.g.:
<xsl:variable name="z" select= "$y + 3" />
is the XSLT version of the mathematical equation above.
Functional programming
A function is a relationship between a set of
inputs and an output. It can also be regarded as an
operation, which when passed specific values as input produces a
specific output.
A functional program is made up of a series of
definitions of functions and other values
[7].
The functional programming style builds upon the declarative
programming style by adding the ability to treat functions as
first-class objects -- that means among other things that functions
can be passed as arguments to other functions. A function can also
return another function as its result.
Higher-order functions in FP languages
A function is higher order if it takes a function as an
argument or returns a function as a result, or both
[7].
Examples.
A classical example is the map function, which can
be defined in Haskell in the following two ways:
map f xs = [ f x | x <- xs ] (1)
or
map f [ ] = [ ]
(2)
map f (x:xs) = f x : map f xs
The map function takes two arguments
-- another function f and a list
xs . The result is a list, every element of which is the
result of applying f to the corresponding element of
the list xs .
If we define f as :
f x = x + 3
and xs as
[1, 2, 3]
Then the value of
map f xs
is
[4, 5, 6]
The map function can be used to produce many other
functions. For example, we can define a function to produce from a
list a new one, whose elements' values are twice as big as the
values of the elements of the input list:
doubleall xs = map (* 2) xs
where (* 2) is a function, that produces a result twice as
bigger as its input.
Another classical example is functional composition:
(.) f g x = f (g x)
Higher-order functions in XSLT -- implementation of template
references
Higher order functions can be implemented in XSLT by way of the
template reference datatype.
Explanation through an example
Templates in XSLT correspond to functions in FP languages. Named
templates are always called by a corresponding xsl:call-template
instruction. Templates with a match attribute are usually selected
for instantiation in a less direct way -- from all templates
matching the node-set specified in an xsl:apply-templates
instruction.
Parameters can be declared for templates using xsl:param
children of xsl:template and actual values for the parameters can
be specified using xsl:with-param children of either
xsl:call-template or xsl:apply-templates.
The result of calling/applying a template is always a particular
output. Therefore, an XSLT template fits precisely the definition
of a function.
As noted before, it is believed that XSLT is not a full
functional programming language, because functions cannot be passed
as parameters to other functions -- not that XSLT lacks functions
at all. We accept that XSLT templates serve the role of functions,
and want to show that functions (templates) can be passed as
parameters to other functions (templates).
Unfortunately, it is not possible to specify through a variable
the name of a template to be called, e.g.:
<xsl:call-template
name="$aTemplate"/>
because according to the W3 XSLT Spec. [8]
the value of the name attribute can only be a Q-Name. A Q-Name is
static and must be completely specified -- it cannot be
dynamically produced by the contents of a variable.
Another way to instantiate a template dynamically has been known
for quite some time
[9], but there's no evidence until now of using it in a
systematic manner. Let's have a template, which matches only a
single node belonging to a unique namespace. In the rest of the
text we'll call such nodes "nodes having a unique type":
File: example1.xsl
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version
= "1.0
" >
<xsl:template match="*[namespace-uri()='8B9C63F4-F4AB5D11-994A0001-B4CD626F']
">
<xsl:param name
= "pX
" />
<xsl:value-of select
= "2 * $pX
" />
</xsl:template>
<xsl:template match="*[namespace-uri()='AB02AC1C-1C65B3FF-77C5FFFE-4B329DA1']
">
<xsl:param name
= "pX
" />
<xsl:value-of select
= "3 * $pX
" />
</xsl:template>
</xsl:stylesheet>
|
We have defined two templates, each matching only a node of a unique type.
The first template produces the value of its input parameter pX
multiplied by 2. The second template produces the value of its input parameter pX
multiplied by 3.
Now we'll define in another stylesheet a template that accepts as parameters
references to two other templates (template references), instantiates the
two templates that are referenced by its template-reference parameters, passing
as parameter to each instantiation its pX
parameter, then as result it produces the sum of the outputs of these
instantiated templates.
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:f1="8B9C63F4-F4AB5D11-994A0001-B4CD626F"
xmlns:f2="AB02AC1C-1C65B3FF-77C5FFFE-4B329DA1" >
<xsl:import href = "example1.xsl" /> <xsl:output method = "text" />
<f1:f1/> <f2:f2/>
<xsl:template match = "/" > <xsl:variable name = "vFun1" select = "document('')/*/f1:*[1]" /> <xsl:variable name = "vFun2" select = "document('')/*/f2:*[1]" />
<xsl:call-template name = "mySum" > <xsl:with-param name = "pX" select = "3" /> <xsl:with-param name = "pFun1" select = "$vFun1" /> <xsl:with-param name = "pFun2" select = "$vFun2" /> </xsl:call-template> </xsl:template>
<xsl:template name = "mySum" > <xsl:param name = "pX" /> <xsl:param name = "pFun1" select = "/.." /> <xsl:param name = "pFun2" select = "/.." />
<xsl:variable name = "vFx_1" > <xsl:apply-templates select = "$pFun1" > <xsl:with-param name = "pX" select = "$pX" /> </xsl:apply-templates> </xsl:variable>
<xsl:variable name = "vFx_2" > <xsl:apply-templates select = "$pFun2" > <xsl:with-param name = "pX" select = "$pX" /> </xsl:apply-templates> </xsl:variable>
<xsl:value-of select = "$vFx_1 + $vFx_2" /> </xsl:template>
</xsl:stylesheet> |
The result produced when this last stylesheet is applied on any (dummy) xml
source document, is:
15
What we have effectively done is we called the template named
"mySum", passing to it two references to
templates, that accept a pX parameter and
produce something out of it. The "mySum"
template successfully instantiates (applies/calls) the templates
that are uniquely identified by the template reference parameters,
then produces the sum of their results. What guarantees that
exactly the necessary templates will be selected by the XSLT
processor is the unique namespace-uri of the nodes they are
matching. The most important property of a template reference is
that it guarantees the unique matching of the template that it is
referencing.
The next two sections demonstrate that by using the template
reference datatype just as described above we can implement even
the most powerful FP design patterns/functions.
|