[xsl] Functional programming in XSLT

Subject: [xsl] Functional programming in XSLT
From: Alexey Gokhberg <alexei@xxxxxxxxxx>
Date: Mon, 12 Mar 2001 10:07:49 +0100
XSLT is frequently called a functional programming language. However,
few important constructions common for functional languages are missing
in XSLT.

At my opinion, adding the following features to XSLT could make it more
suitable for the functional programming.


1. Lambda-elements
------------------

Defines anonymous functions, which may be passed to another functions as
parameters.

Syntax:

<xsl:lambda>
  <xsl:param ...>
  <xsl:param ...>
    . . .
  <xsl:return>
</xsl:lambda>

(The prefix "xsl:" is used here for simplicity. Another prefix must be
chosen when these features are implemented as extensions of XSLT 1.0).

The xsl:lambda element contains zero or more xsl:param children and
exactly one xsl:return child. The xsl:param children specify function
parameters and have usual XSLT syntax and semantics. The xsl:return
element specifies the function return value and may have one of two
formats:

<xsl:return select="expression"/>

or

<xsl:return>
  . . .
  <!-- Content: template -->
  . . .
</xsl:return>

When the first format is used, the result of evaluating "expression" is
returned. When the second format is used, the result tree fragment
obtained by instantiation of the element content is returned. (These
rules are similar to those defined for xsl:variable in the standard
XSLT).

When the xsl:lambda element is instantiated, the xsl:lambda element node
is simply copied, with all its children, to the result tree.

Invocation of the lambda function is performed by the extension function
"xsl:call". This function requires one or more arguments. The first
argument must be a result tree fragment containing a single child node;
this node must be the xsl:lambda element. Following arguments, if
present, are interpreted as parameter values. The xsl:call function is
evaluated as follows:

a) the body of xsl:lambda element specified by the first argument is
extracted

b) values specified by the following arguments are assigned to
parameters

c) the xsl:return element is instantiated; the result of instantiation
will be returned by xsl:call

Arguments of xsl:call are mapped to parameters of xsl:lambda based on
their position, therefore the order of xsl:param elements is
significant.

The following variables are visible inside the xsl:return element:

- global variables and parameters
- parameters declared by xsl:param siblings
- variables defined locally inside this xsl:return


2. User-defined functions
-------------------------

Syntax:

<xsl:define name="qname">
  <xsl:param ...>
  <xsl:param ...>
    . . .
  <xsl:return>
</xsl:define>

The xsl:define element defines the extension function with the name
"qname". Extension functions can be defined at the top-level and within
templates. The scope rules for extension function names are the same as
for variable names defined with xsl:variable. The xsl:param and
xsl:return children have the same syntax and similar semantics as
described in case of xsl:lambda.


3. Examples
-----------

A. Filtering elements in a node-set.

This function accepts two arguments: a node-set and a unary
lambda-function. It walks through the node-set, copying to the result
tree the nodes, for which the lambda-function returns true.

<xsl:define name="set:filter>
  <xsl:param name="set"/>
  <xsl:param name="lambda"/>
  <xsl:return>
    <xsl:for-each select="$set">
      <xsl:if test="xsl:call($lambda, .)">
        <xsl:copy-of select="."/>
      </xsl:if>
    </xsl:for-each>
  </xsl:return>
</xsl:define>


B. Mapping elements in a node-set

This function accepts two arguments: a node-set and a unary
lambda-function. It walks throug the node-set, applying the function to
each node and copying the result to the result tree.

<xsl:define name="set:map>
  <xsl:param name="set"/>
  <xsl:param name="lambda"/>
  <xsl:return>
    <xsl:for-each select="$set">
      <xsl:copy-of select="xsl:call($lambda, .)">
    </xsl:for-each>
  </xsl:return>
</xsl:define>


C. Reducing a node-set to a single value

This function accepts three arguments: a node-set, a binary
lambda-function, and an initial value. It first applies the function to
the initial value and the first node in the list; then it applies the
function to the result and the second node in the list, etc.

<xsl:define name="set:reduce">
  <xsl:param name="set"/>
  <xsl:param name="lambda"/>
  <xsl:param name="init"/>
  <xsl:return>
    <xsl:choose>
      <xsl:when test="count($set) = 0">
        <xsl:value-of select="$init"/>
      </xsl:when>
      <xsl:otherwise>
        <xsl:value-of 
            select="set:reduce(
                $set[position() &gt; 1],
                $lambda,
                xsl:call($lambda, $init, $set[1]))"/>
      </xsl:otherwise>
    </xsl:choose>
  </xsl:return>
</xsl:define>

Now, summation of values in a node-set can be defined as

<xsl:define name=set:sum">
  <xsl:param name="set"/>
  <xsl:param name="lambda"/>
  <xsl:return>
    <xsl:variable name="sum">
      <xsl:lambda>
        <xsl:param name="x"/>
        <xsl:param name="y"/>
        <xsl:return select="$x + $y"/>
      </xsl:lambda>
    </xsl:variable>
    <xsl:value-of select="set:reduce($set, $sum, 0)"/>
  </xsl:return>
</xsl:define>

For the given node set "set", the sum of values for all "value" elements
can now be obtained with:

<xsl:variable name="value">
  <xsl:lambda>
    <xsl:param name="node">
    <xsl:return select="$node/@value"/>
  </xsl:lambda>
</xsl:variable>

<xsl:value-of select="set:sum($set, $value)"/>

Due to its importance, "set:reduce" may be implemented as the built-in
function.


Alexey Gokhberg
Unicorn Enterprises SA

 XSL-List info and archive:  http://www.mulberrytech.com/xsl/xsl-list


Current Thread