XPath 3.0 has introduced several new function enhancements:
In XPath 3.0, user-defined functions are created as inline (anonymous) functions.
Inline functions are 'anonymous', meaning that the function is unnamed.
An inline function definition begins with the keyword 'function', followed by an optional list of parameters. Each parameter may also specify a type. The parameter list is followed by an optional return type, followed by the body of the function.
function () {'hello world'}
function ($arg) {$arg * 2}
function ($arg as xs:integer) as xs:integer {$arg * 2}
Because inline functions have no name, they are usually defined in a 'let' expression bound to a variable. The variable acts as an identifier for the function so that it can be used. The scope of the function is the let expression in which the function is defined.
let $double_funct := function ($arg) { $arg * 2 } return $double_funct(45)
90
Functions have been elevated to first class status in XPath 3.0. This means that:
An example of a higher order function is shown below:
let $temp := function($t as xs:integer, $funct as function(xs:integer) as xs:decimal) as xs:decimal { $funct($t) }, $f_to_c := function($t as xs:integer) as xs:decimal { (5 div 9)*($t -32) }, $c_to_f := function($t as xs:integer) as xs:decimal { (9 div 5)*($t) + 32 } return ('fahrenheit to celsius: ', $temp(68, $f_to_c), ', celsius to fahrenheit: ', $temp(0 , $c_to_f))
('fahrenheit to celsius: ', 20 ,'celsius to fahrenheit: ', 32)
At the end of this chapter the built-in higher order functions (i.e. higher order functions which are built-in to XPath 3.0) will be covered.
XPath 3.0 contains several new built-in functions. Some of these new built-in functions are higher order functions. The built-in higher order functions are covered in this subsection.
The built-in higher order functions include:
The 'for-each' function is a built-in higher order function which takes two arguments and returns a sequence of items.
It applies the function supplied in the second argument to each item in the sequence supplied in the first argument.
The first argument is a sequence of items.
The second argument is a function which takes one argument, an item, and returns a sequence of items.
for-each(1 to 5, function($arg) {$arg * 10})
(10, 20, 20, 40, 50)
The 'filter' function is a built-in higher order function which takes two arguments and returns a sequence of items.
It filters the sequence of items in the first argument to only those items for which the function supplied in the second argument returns 'true'.
The first argument is a sequence of items.
The second argument is a function which takes an item as an argument and returns an 'xs:boolean' value.
filter(1 to 5, function($arg) {$arg mod 2 = 0})
(2, 4)
The 'fold-left' function is a built-in higher order function which takes three arguments and returns a sequence of items.
A fold reduces a sequence of items/values to one value. 'fold-left' process the sequence from left to right.
The first argument '$seq' is a sequence of items to be processed (from left to right).
The second argument is a base value.
The third argument is a function '$func'. The function takes two arguments ('$arg1' and '$arg2') which are both sequences of items. '$func' also returns a sequence of items.
'fold-left' takes a function '$func' which operates on a pair of values 'arg1' and 'arg2'. 'fold-left' applies '$func' repeatedly, with an accumulated result as the first argument '$arg1', and the next item in the sequence '$seq' as the second argument 'arg2'. The accumulated result is initially set to the base value, which by convention causes the function to return the value of the other argument unchanged. For example, in the case of addition the base value might be 0, because 0 + $arg2 = $arg2 (i.e. $arg2 is unchanged), whereas in the case of multiplication a base value of 1 would be more appropriate because 1 * $arg2 = $arg2, and therefore 1 causes '$arg2' to remain unchanged.
fold-left(1 to 5, 1, function($arg1, $arg2) {$arg1 * $arg2})
120
fold-left(1 to 5, 0, function($arg1, $arg2) {$arg1 * $arg2})
0
fold-left(1 to 5, 0, function($arg1, $arg2) {$arg1 + $arg2})
15
fold-left(1 to 5, 0, function($arg1, $arg2) {$arg1 - $arg2})
-15
fold-left(('a', 'b', 'c'), 'z' , function($arg1, $arg2) {concat($arg1, $arg2)})
'zabc'
The 'fold-right' function is a built-in higher order function which takes three arguments and returns a sequence of items.
A fold reduces a sequence of items/values to one value. 'fold-right' process the sequence from right to left.
The first argument '$seq' is a sequence of items to be processed (from right to left).
The second argument is a base value.
The third argument is a function '$func'. The function takes two arguments ($arg1 and $arg2) which are both sequences of items. '$func' also returns a sequence of items.
'fold-right' takes a function '$func' which operates on a pair of values '$arg1' and '$arg2'. 'fold-right' applies '$func' repeatedly, with the next item in the sequence as the first argument '$arg1', and the result of processing the remainder of the sequence as the second argument '$arg2'. The accumulated result is initially set to the base value, which by convention causes the function to return the value of the other argument unchanged. For example, in the case of addition the base value might be 0, because 0 + $arg2 = $arg2 (i.e. '$arg2' is unchanged), whereas in the case of multiplication a base value of 1 would be more appropriate because 1 * $arg2 = $arg2, and therefore 1 causes $arg2 to remain unchanged.
fold-right(1 to 5, 0, function($arg1, $arg2) {$arg1 + $arg2})
15
fold-right(1 to 5, 0, function($arg1, $arg2) {$arg1 - $arg2})
3
fold-right(('a', 'b', 'c'), 'z' , function($arg1, $arg2) {concat($arg1, $arg2)})
'abcz'
The 'for-each-pair' function is a built-in higher order function which takes three arguments and returns a sequence of items.
It applies the function passed as the third argument to consecutive items from the sequence of items in the first and second arguments and returns the resulting sequence.
for-each-pair((1, 10, 100), (2, 5, 10), function($arg1, $arg2) {$arg1 * $arg2}))
(2, 50, 1000)
for-each-pair((1, 10, 100), (2, 5), function($arg1, $arg2) {$arg1 * $arg2}))
(2, 50)
Function composition is when the output of one function is used as the input for another function. For example we can create a function 'a' which take two functions 'b' and 'c' as arguments, where the output of function 'c' will be used as the input of function 'b'. This is best demonstrated by example.
let $z := function($a as function(*), $b as function(*)) as function(*) { function($c as item()*) { $a($b($c)) } }, $div10 := function($x as xs:double) as xs:double { $x div 10 }, $pow2 := function($y as xs:double) as xs:double { $y * $y } return ($z($div10, $pow2)(5), $z($pow2, $div10)(5))
(2.5, 0.25)
A partial function application is when a function with many arguments uses the '?' placeholder to indicate that the argument will be provided at a 'later point'.
for-each-pair( 1 to 5, ( 'London', 'New York', 'Vienna', 'Paris', 'Tokyo' ), concat( ?, ' ', ? ) )
('1 London', '2 New York', '3 Vienna', '4 Paris', '5 Tokyo')
A partial function implementation is when a function with many arguments binds some of its arguments to values so that a new function can be created from this function which only has to pass the remaining arguments. Calling the new function with the 'remaining' arguments would therefore be equivalent to calling the 'base' function with all arguments.
let $tax_rate := function($rate as xs:integer, $amount as xs:decimal) as xs:decimal { ($rate div 100) * $amount }, $income_tax := function($amount as xs:decimal) as xs:decimal { $tax_rate(15, ?)($amount) }, $luxury_tax := function($amount as xs:integer) as xs:decimal { $tax_rate(50, ?)($amount) } return ($income_tax(300), $luxury_tax(50))
(45, 25)
By now we know that a higher order function can take functions as parameters and/or return a function. If a function 'a' returns a function 'b' and function 'b' contains data from function 'a' this is known as a closure.
let $date := function($upperdate as xs:date) as function(xs:date, xs:string) as xs:string { function($birthdate as xs:date, $name as xs:string) as xs:string { 'On ' || fn:adjust-date-to-timezone($upperdate, ()) || ', ' || $name || ' was ' || fn:days-from-duration($upperdate - $birthdate ) || ' days old' } }, $days_old:= $date(fn:current-date()) return ($days_old(xs:date('1957-08-13'), 'Mary' ), $days_old(xs:date('1990-12-13'), 'John' ))
('On 2014-10-24, Mary was 20890 days old', 'On 2014-10-24, John was 8715 days old')