Difference between revisions of "Higher-Order Functions Module"

From BaseX Documentation
Jump to navigation Jump to search
m (Text replacement - "<syntaxhighlight lang="xquery">" to "<pre lang='xquery'>")
Tags: Mobile web edit Mobile edit
 
(18 intermediate revisions by 2 users not shown)
Line 1: Line 1:
 
This [[Module Library|XQuery Module]] adds some useful higher-order functions, additional to the [[Higher-Order Functions]] provided by the official specification.
 
This [[Module Library|XQuery Module]] adds some useful higher-order functions, additional to the [[Higher-Order Functions]] provided by the official specification.
 +
 +
With {{Announce|Version 11}}, many functions have been removed in favor of new features of XQuery 4:
 +
 +
{|
 +
|- valign="top"
 +
| '''BaseX 10'''
 +
| '''XQuery 4'''
 +
|- valign="top"
 +
| {{Code|hof:drop-while}}
 +
| [https://qt4cg.org/specifications/xpath-functions-40/Overview.html#func-drop-while <code>fn:items-starting-where</code>]
 +
|- valign="top"
 +
| {{Code|hof:id}}
 +
| [https://qt4cg.org/specifications/xpath-functions-40/Overview.html#func-identity <code>fn:identity</code>]
 +
|- valign="top"
 +
| {{Code|hof:until}}
 +
| [https://qt4cg.org/specifications/xpath-functions-40/Overview.html#func-iterate-while <code>fn:iterate-while</code>]
 +
|- valign="top"
 +
| {{Code|hof:take-while}}
 +
| [https://qt4cg.org/specifications/xpath-functions-40/Overview.html#func-take-while <code>fn:items-before</code>]
 +
|}
  
 
=Conventions=
 
=Conventions=
Line 10: Line 30:
  
 
{| width='100%'
 
{| width='100%'
|-
+
|- valign="top"
| width='120' | '''Signatures'''
+
| width='120' | '''Signature'''
|{{Func|hof:fold-left1|$seq as item()+, $f as function(item()*, item()) as item()*|item()*}}
+
|<pre>hof:fold-left1(
|-
+
  $input  as item()+,
 +
  $action  as function(item()*, item()) as item()*
 +
) as item()*</pre>
 +
|- valign="top"
 
| '''Summary'''
 
| '''Summary'''
 
|Works the same as [[Higher-Order Functions#fn:fold-left|fn:fold-left]], but does not need a seed, because the sequence must be non-empty.
 
|Works the same as [[Higher-Order Functions#fn:fold-left|fn:fold-left]], but does not need a seed, because the sequence must be non-empty.
|-
+
|- valign="top"
 
| '''Examples'''
 
| '''Examples'''
 
|
 
|
 
* {{Code|hof:fold-left1(1 to 10, function($a, $b) { $a + $b })}} returns {{Code|55}}.
 
* {{Code|hof:fold-left1(1 to 10, function($a, $b) { $a + $b })}} returns {{Code|55}}.
 
* {{Code|hof:fold-left1((), function($a, $b) { $a + $b })}} throws {{Code|XPTY0004}}, because {{Code|$seq}} has to be non-empty.
 
* {{Code|hof:fold-left1((), function($a, $b) { $a + $b })}} throws {{Code|XPTY0004}}, because {{Code|$seq}} has to be non-empty.
|}
 
 
==hof:until==
 
 
{| width='100%'
 
|-
 
| width='120' | '''Signatures'''
 
|{{Func|hof:until|$pred as function(item()*) as xs:boolean, $f as function(item()*) as item()*, $start as item()*|item()*}}
 
|-
 
| '''Summary'''
 
|Applies the predicate function {{Code|$pred}} to {{Code|$start}}. If the result is {{Code|false}}, {{Code|$f}} is invoked with the start value – or, subsequently, with the result of this function – until the predicate function returns {{Code|true()}}.
 
|-
 
| '''Examples'''
 
|
 
* Doubles a numeric value until a maximum is reached:
 
<syntaxhighlight lang="xquery">
 
hof:until(
 
  function($output) { $output ge 1000 },
 
  function($input ) { 2 * $input },
 
  1
 
)
 
</syntaxhighlight>
 
* Calculates the square-root of a number by iteratively improving an initial guess:
 
<syntaxhighlight lang="xquery">
 
let $sqrt := function($input as xs:double) as xs:double {
 
  hof:until(
 
    function($result) { abs($result * $result - $input) < 0.00001 },
 
    function($guess) { ($guess + $input div $guess) div 2 },
 
    $input
 
  )
 
}
 
return $sqrt(25)
 
</syntaxhighlight>
 
* Returns {{Code|OK}}, as the predicate is evaluated first:
 
<syntaxhighlight lang="xquery">
 
hof:until(
 
  function($_) { true() },
 
  function($_) { error() },
 
  'OK'
 
)
 
</syntaxhighlight>
 
 
|}
 
|}
  
Line 67: Line 49:
  
 
{| width='100%'
 
{| width='100%'
|-
+
|- valign="top"
| width='120' | '''Signatures'''
+
| width='120' | '''Signature'''
|{{Func|hof:scan-left|$seq as item()*, $start as item()*, $f as function(item()*, item()) as item()*|item()*}}
+
|<pre>hof:scan-left(
|-
+
  $input  as item()*,
 +
  $zero    as item()*,
 +
  $action  as function(item()*, item()) as item()*
 +
) as item()*</pre>
 +
|- valign="top"
 
| '''Summary'''
 
| '''Summary'''
 
|This function is similar to [[Higher-Order Functions#fn:fold-left|fn:fold-left]], but it returns a list of successive reduced values from the left. It is equivalent to:
 
|This function is similar to [[Higher-Order Functions#fn:fold-left|fn:fold-left]], but it returns a list of successive reduced values from the left. It is equivalent to:
<syntaxhighlight lang="xquery">
+
<pre lang='xquery'>
declare function hof:scan-left($seq, $acc, $f) {
+
declare function hof:scan-left($input, $acc, $action) {
   if(empty($seq)) then $acc else (
+
   if(empty($input)) then $acc else (
 
     $acc,
 
     $acc,
     hof:scan-left(tail($seq), $f($acc, head($seq)), $f)
+
     hof:scan-left(tail($input), $action($acc, head($input)), $action)
 
   )
 
   )
 
};
 
};
</syntaxhighlight>
+
</pre>
|-
+
|- valign="top"
 
| '''Examples'''
 
| '''Examples'''
 
|
 
|
 
* Returns triangular numbers:
 
* Returns triangular numbers:
<syntaxhighlight lang="xquery">
+
<pre lang='xquery'>
 
hof:scan-left(1 to 10, 0, function($a, $b) { $a + $b })
 
hof:scan-left(1 to 10, 0, function($a, $b) { $a + $b })
</syntaxhighlight>
+
</pre>
|}
 
 
 
==hof:take-while==
 
 
 
{| width='100%'
 
|-
 
| width='120' | '''Signatures'''
 
|{{Func|hof:take-while|$seq as item()*, $pred as function(item()) as xs:boolean|item()*}}
 
|-
 
| '''Summary'''
 
|The function returns items of <code>$seq</code> as long as the predicate <code>$pred</code> is satisfied. It is equivalent to:
 
<syntaxhighlight lang="xquery">
 
declare function hof:take-while($seq, $pred) {
 
  if(empty($seq) or not($pred(head($seq)))) then () else (
 
    head($seq),
 
    hof:take-while(tail($seq), $pred)
 
  )
 
};
 
</syntaxhighlight>
 
|-
 
| '''Examples'''
 
|
 
* Computes at most 100 random integers, but stops if an integer is smaller than 10:
 
<syntaxhighlight lang="xquery">
 
hof:take-while(
 
  (1 to 100) ! random:integer(50),
 
  function($x) { $x >= 10 }
 
)
 
</syntaxhighlight>
 
|}
 
 
 
==hof:drop-while==
 
 
 
{{Mark|Introduced with Version 9.5.}}
 
 
 
{| width='100%'
 
|-
 
| width='120' | '''Signatures'''
 
|{{Func|hof:drop-while|$seq as item()*, $pred as function(item()) as xs:boolean|item()*}}
 
|-
 
| '''Summary'''
 
|The function skips all items of <code>$seq</code> until the predicate <code>$pred</code> is not satisfied anymore. It is equivalent to:
 
<syntaxhighlight lang="xquery">
 
declare function hof:drop-while($seq, $pred) {
 
  if($pred(head($seq))) then (
 
    hof:drop-while(tail($seq), $pred)
 
  ) else (
 
    $seq
 
  )
 
};
 
</syntaxhighlight>
 
|-
 
| '''Examples'''
 
|Returns the name of the first file that does not exist on disk:
 
<syntaxhighlight lang="xquery">
 
hof:drop-while(
 
  (1 to 1000) ! (. || '.log'),
 
  file:exists#1
 
)[1]
 
</syntaxhighlight>
 
 
|}
 
|}
  
Line 155: Line 81:
  
 
{| width='100%'
 
{| width='100%'
|-
+
|- valign="top"
| width='120' | '''Signatures'''
+
| width='120' | '''Signature'''
|{{Func|hof:top-k-by|$seq as item()*, $sort-key as function(item()) as item(), $k as xs:integer|item()*}}
+
|<pre>hof:top-k-by(
|-
+
  $input  as item()*,
 +
  $key   as function(item()) as item(),
 +
  $k     as xs:integer
 +
) as item()*</pre>
 +
|- valign="top"
 
| '''Summary'''
 
| '''Summary'''
|Returns the {{Code|$k}} items in {{Code|$seq}} that are greatest when sorted by the result of {{Code|$f}} applied to the item. The function is a much more efficient implementation of the following scheme:
+
|Returns the {{Code|$k}} items in {{Code|$input}} that are greatest when sorted by the result of {{Code|$key}} applied to the item. The function is a much more efficient implementation of the following scheme:
<syntaxhighlight lang="xquery">
+
<pre lang='xquery'>
(for $x in $seq
+
(for $item in $input
  order by $sort-key($x) descending
+
  order by $key($item) descending
  return $x
+
  return $item
 
)[position() <= $k]
 
)[position() <= $k]
</syntaxhighlight>
+
</pre>
|-
+
|- valign="top"
 
| '''Examples'''
 
| '''Examples'''
 
|
 
|
Line 178: Line 108:
  
 
{| width='100%'
 
{| width='100%'
|-
+
|- valign="top"
| width='120' | '''Signatures'''
+
| width='120' | '''Signature'''
|{{Func|hof:top-k-with|$seq as item()*, $lt as function(item(), item()) as xs:boolean, $k as xs:integer|item()*}}
+
|<pre>hof:top-k-with(
|-
+
  $input      as item()*,
 +
  $comparator  as function(item(), item()) as xs:boolean,
 +
  $k           as xs:integer
 +
) as item()*</pre>
 +
|- valign="top"
 
| '''Summary'''
 
| '''Summary'''
|Returns the {{Code|$k}} items in {{Code|$seq}} that are greatest when sorted in the order of the ''less-than'' predicate {{Code|$lt}}. The function is a general version of {{Code|hof:top-k-by($seq, $sort-key, $k)}}.
+
|Returns the {{Code|$k}} items in {{Code|$input}} that are greatest when sorted in the order of the ''less-than'' predicate {{Code|$comparator}}. The function is a general version of {{Function||hof:top-k-by}}.
|-
+
|- valign="top"
 
| '''Examples'''
 
| '''Examples'''
 
|
 
|
Line 191: Line 125:
 
|}
 
|}
  
=IDs=
+
=Identity=
 
 
==hof:id==
 
 
 
{| width='100%'
 
|-
 
| width='120' | '''Signatures'''
 
|{{Func|hof:id|$expr as item()*|item()*}}
 
|-
 
| '''Summary'''
 
|Returns its argument unchanged. This function isn't useful on its own, but can be used as argument to other higher-order functions.
 
|-
 
| '''Examples'''
 
|
 
* {{Code|hof:id(1 to 5)}} returns {{Code|1 2 3 4 5}}
 
* With higher-order functions:
 
<syntaxhighlight lang="xquery">
 
let $sort := sort(?, (), hof:id#1)
 
let $reverse-sort := sort(?, (), function($x) { -$x })
 
return (
 
  $sort((1, 5, 3, 2, 4)),
 
  '|',
 
  $reverse-sort((1, 5, 3, 2, 4))
 
)
 
</syntaxhighlight>
 
returns: <code>1 2 3 4 5 | 5 4 3 2 1</code>
 
|}
 
  
 
==hof:const==
 
==hof:const==
  
 
{| width='100%'
 
{| width='100%'
|-
+
|- valign="top"
| width='120' | '''Signatures'''
+
| width='120' | '''Signature'''
|{{Func|hof:const|$expr as item()*, $ignored as item()*|item()*}}
+
|<pre>hof:const(
|-
+
  $input  as item()*,
 +
  $ignore  as item()*
 +
) as item()*</pre>
 +
|- valign="top"
 
| '''Summary'''
 
| '''Summary'''
|Returns its first argument unchanged and ignores the second. This function isn't useful on its own, but can be used as argument to other higher-order functions, e.g. when a function combining two values is expected and one only wants to retain the left one.
+
|Returns its first argument unchanged and ignores the second. This function isn’t useful on its own, but can be used as argument to other higher-order functions, e.g., when a function combining two values is expected and one only wants to retain the left one.
|-
+
|- valign="top"
 
| '''Examples'''
 
| '''Examples'''
 
|
 
|
 
* {{Code|hof:const(42, 1337)}} returns {{Code|42}}.
 
* {{Code|hof:const(42, 1337)}} returns {{Code|42}}.
 
* With higher-order functions:
 
* With higher-order functions:
<syntaxhighlight lang="xquery">
+
<pre lang='xquery'>
 
let $zip-sum := function($f, $seq1, $seq2) {
 
let $zip-sum := function($f, $seq1, $seq2) {
 
   sum(for-each-pair($seq1, $seq2, $f))
 
   sum(for-each-pair($seq1, $seq2, $f))
Line 243: Line 154:
 
   $sum-left((1, 1, 1, 1, 1), 1 to 5)
 
   $sum-left((1, 1, 1, 1, 1), 1 to 5)
 
)
 
)
</syntaxhighlight>
+
</pre>
 
* Another use-case: When inserting a key into a map, {{Code|$f}} decides how to combine the new value with a possibly existing old one. {{Code|hof:const}} here means ignoring the old value, so that's normal insertion.
 
* Another use-case: When inserting a key into a map, {{Code|$f}} decides how to combine the new value with a possibly existing old one. {{Code|hof:const}} here means ignoring the old value, so that's normal insertion.
<syntaxhighlight lang="xquery">
+
<pre lang='xquery'>
 
let $insert-with := function($f, $map, $k, $v) {
 
let $insert-with := function($f, $map, $k, $v) {
 
   let $old := $map($k)
 
   let $old := $map($k)
Line 258: Line 169:
 
   $ins($map, 'foo', 42)('foo')
 
   $ins($map, 'foo', 42)('foo')
 
)
 
)
</syntaxhighlight>
+
</pre>
 
returns {{Code|3 42}}
 
returns {{Code|3 42}}
 
|}
 
|}
  
 
=Changelog=
 
=Changelog=
 +
 +
;Version 11.0
 +
* Removed: {{Code|hof:until}} (replaced with {{Code|fn:iterate-while}}, {{Code|hof:if}} (replaced with {{Code|fn:identity}}, {{Code|hof:drop-while}} (replaced with {{Code|fn:items-starting-where}}), {{Code|hof:take-while}} (replaced with {{Code|fn:items-before}})
  
 
;Version 9.5
 
;Version 9.5
* Added: [[#hof:drop-while|hof:drop-while]]
+
* Added: {{Function||hof:drop-while}}
  
 
;Version 8.1
 
;Version 8.1
* Added: [[#hof:scan-left|hof:scan-left]], [[#hof:take-while|hof:take-while]]
+
* Added: {{Function||hof:scan-left}}, {{Function||hof:take-while}}
  
 
;Version 7.2
 
;Version 7.2
* Added: [[#hof:top-k-by|hof:top-k-by]], [[#hof:top-k-with|hof:top-k-with]]
+
* Added: {{Function||hof:top-k-by}}, {{Function||hof:top-k-with}}
 
* Removed: hof:iterate
 
* Removed: hof:iterate
  
 
;Version 7.0
 
;Version 7.0
 
* module added
 
* module added

Latest revision as of 18:35, 1 December 2023

This XQuery Module adds some useful higher-order functions, additional to the Higher-Order Functions provided by the official specification.

With Version 11, many functions have been removed in favor of new features of XQuery 4:

BaseX 10 XQuery 4
hof:drop-while fn:items-starting-where
hof:id fn:identity
hof:until fn:iterate-while
hof:take-while fn:items-before

Conventions[edit]

All functions in this module are assigned to the http://basex.org/modules/hof namespace, which is statically bound to the hof prefix.

Loops[edit]

hof:fold-left1[edit]

Signature
hof:fold-left1(
  $input   as item()+,
  $action  as function(item()*, item()) as item()*
) as item()*
Summary Works the same as fn:fold-left, but does not need a seed, because the sequence must be non-empty.
Examples
  • hof:fold-left1(1 to 10, function($a, $b) { $a + $b }) returns 55.
  • hof:fold-left1((), function($a, $b) { $a + $b }) throws XPTY0004, because $seq has to be non-empty.

hof:scan-left[edit]

Signature
hof:scan-left(
  $input   as item()*,
  $zero    as item()*,
  $action  as function(item()*, item()) as item()*
) as item()*
Summary This function is similar to fn:fold-left, but it returns a list of successive reduced values from the left. It is equivalent to:
declare function hof:scan-left($input, $acc, $action) {
  if(empty($input)) then $acc else (
    $acc,
    hof:scan-left(tail($input), $action($acc, head($input)), $action)
  )
};
Examples
  • Returns triangular numbers:
hof:scan-left(1 to 10, 0, function($a, $b) { $a + $b })

Sorting[edit]

hof:top-k-by[edit]

Signature
hof:top-k-by(
  $input  as item()*,
  $key    as function(item()) as item(),
  $k      as xs:integer
) as item()*
Summary Returns the $k items in $input that are greatest when sorted by the result of $key applied to the item. The function is a much more efficient implementation of the following scheme:
(for $item in $input
 order by $key($item) descending
 return $item
)[position() <= $k]
Examples
  • hof:top-k-by(1 to 1000, hof:id#1, 5) returns 1000 999 998 997 996
  • hof:top-k-by(1 to 1000, function($x) { -$x }, 3) returns 1 2 3
  • hof:top-k-by(<x a='1' b='2' c='3'/>/@*, xs:integer#1, 2)/node-name() returns c b

hof:top-k-with[edit]

Signature
hof:top-k-with(
  $input       as item()*,
  $comparator  as function(item(), item()) as xs:boolean,
  $k           as xs:integer
) as item()*
Summary Returns the $k items in $input that are greatest when sorted in the order of the less-than predicate $comparator. The function is a general version of hof:top-k-by.
Examples
  • hof:top-k-with(1 to 1000, function($a, $b) { $a lt $b }, 5) returns 1000 999 998 997 996
  • hof:top-k-with(-5 to 5, function($a, $b) { abs($a) gt abs($b) }, 5) returns 0 1 -1 2 -2

Identity[edit]

hof:const[edit]

Signature
hof:const(
  $input   as item()*,
  $ignore  as item()*
) as item()*
Summary Returns its first argument unchanged and ignores the second. This function isn’t useful on its own, but can be used as argument to other higher-order functions, e.g., when a function combining two values is expected and one only wants to retain the left one.
Examples
  • hof:const(42, 1337) returns 42.
  • With higher-order functions:
let $zip-sum := function($f, $seq1, $seq2) {
  sum(for-each-pair($seq1, $seq2, $f))
}
let $sum-all  := $zip-sum(function($a, $b) { $a + $b }, ?, ?)
let $sum-left := $zip-sum(hof:const#2, ?, ?)
return (
  $sum-all((1, 1, 1, 1, 1), 1 to 5),
  $sum-left((1, 1, 1, 1, 1), 1 to 5)
)
  • Another use-case: When inserting a key into a map, $f decides how to combine the new value with a possibly existing old one. hof:const here means ignoring the old value, so that's normal insertion.
let $insert-with := function($f, $map, $k, $v) {
  let $old := $map($k)
  let $new := if($old) then $f($v, $old) else $v
  return map:merge(($map, map:entry($k, $new)))
}
let $map := map { 'foo': 1 }
let $add := $insert-with(function($a, $b) { $a + $b }, ?, ?, ?)
let $ins := $insert-with(hof:const#2, ?, ?, ?)
return (
  $add($map, 'foo', 2)('foo'),
  $ins($map, 'foo', 42)('foo')
)

returns 3 42

Changelog[edit]

Version 11.0
  • Removed: hof:until (replaced with fn:iterate-while, hof:if (replaced with fn:identity, hof:drop-while (replaced with fn:items-starting-where), hof:take-while (replaced with fn:items-before)
Version 9.5
Version 8.1
Version 7.2
Version 7.0
  • module added