How Does XSLT 3.0 Support Higher-Order Functions?

XSLT 3.0, in conjunction with XPath 3.0 and 3.1, fundamentally modernizes XML transformations by treating functions as first-class citizens. Through dynamic function items, inline function expressions, and higher-order functions like fn:for-each, fn:filter, and fn:fold-left, developers can write modular, functional code directly within stylesheets without relying on verbose template workarounds.

First-Class Functions and Named References

In earlier versions of XSLT and XPath, functions could only be called statically by name. XSLT 3.0 introduces function items as a discrete data type. This allows functions to be assigned to variables, passed as arguments to other functions or templates, and returned as results.

Named functions are referenced using the literal function reference syntax QName#arity:

<!-- Storing a function reference in a variable -->
<xsl:variable name="cleaner" select="fn:normalize-space#1" />

<!-- Invoking the function item dynamically -->
<xsl:value-of select="$cleaner('  sample text  ')" />

The arity (#1 in the example above) is required because XPath supports function overloading by argument count.

Inline Function Expressions

Inline functions (also known as anonymous functions or lambdas) allow developers to declare functions locally without defining a standalone xsl:function declaration.

The syntax for an inline function follows this structure:

function($parameter as type) as return-type { expression }

Inline functions capture their lexical scope (closures), meaning they can reference local variables and parameters from the surrounding template or stylesheet context:

<xsl:variable name="multiplier" select="10" />
<xsl:variable name="scale" select="function($val as xs:integer) as xs:integer { $val * $multiplier }" />

<xsl:value-of select="$scale(5)" /> <!-- Outputs 50 -->

Standard Higher-Order Functions

XPath 3.0 and 3.1 provide built-in higher-order functions in the fn namespace that accept function items as arguments to manipulate sequences:

Practical Transformation Example

Higher-order functions eliminate complex recursive templates when aggregating or filtering data:

<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
                xmlns:xs="http://www.w3.org/2001/XMLSchema"
                xmlns:fn="http://www.w3.org/2005/xpath-functions"
                version="3.0">

    <xsl:template match="orders">
        <!-- Filter high-value items and double their value using inline functions -->
        <xsl:variable name="prices" select="item/@price/xs:decimal(.)" />
        
        <xsl:variable name="expensive" 
                      select="fn:filter($prices, function($p) { $p gt 50.0 })" />
                      
        <xsl:variable name="discountedSum" 
                      select="fn:fold-left($expensive, 0, function($total, $p) { $total + ($p * 0.9) })" />

        <total-discounted-value>
            <xsl:value-of select="$discountedSum" />
        </total-discounted-value>
    </xsl:template>

</xsl:stylesheet>

By integrating first-class function items, closures, and higher-order sequence functions, XSLT 3.0 enables cleaner abstractions, higher code reuse, and true declarative functional programming across stylesheet pipelines.