This is an archived snapshot of W3C's public bugzilla bug tracker, decommissioned in April 2019. Please see the home page for more details.

Bug 21797 - Ordering of parameters in '16.2 Basic higher-order functions'
Summary: Ordering of parameters in '16.2 Basic higher-order functions'
Status: RESOLVED FIXED
Alias: None
Product: XPath / XQuery / XSLT
Classification: Unclassified
Component: Functions and Operators 3.0 (show other bugs)
Version: Candidate Recommendation
Hardware: All All
: P2 minor
Target Milestone: ---
Assignee: Michael Kay
QA Contact: Mailing list for public feedback on specs from XSL and XML Query WGs
URL:
Whiteboard:
Keywords:
Depends on:
Blocks:
 
Reported: 2013-04-23 17:45 UTC by Adam Retter
Modified: 2013-05-07 17:09 UTC (History)
1 user (show)

See Also:


Attachments

Description Adam Retter 2013-04-23 17:45:04 UTC
At present the following functions all apply a function to each item in a sequence, which yields a new sequence (NOTE - fn:map-pairs applies the function to two sequences).

fn:map
fn:filter
fn:fold-left
fn:fold-right
fn:map-pairs

However, the first argument is always the function at the moment. This is fine for named functions, however for anonymous/inline functions this can perhaps make your XQuery code harder to read. I propose moving the function argument from the first argument of each of the above functions to the last argument.


Example 1, function argument first (current approach in spec.) -

map(function($x) {$x * 2 }, $sequence)


Example 2, function argument last (proposal) -

map($sequence, function($x) {$x * 2 })


There is not perhaps not too much discernible difference above, but as the anonymous/inline functions become longer and more complex, it gets harder to see what you are operating on using the current approach in the spec:

Example 3, function argument first (current approach in spec.) -

map(
  function($pos as element(pos)) {
    let $y := sin($pos/angle),
    $dy := $y * $pos/angular-distance,
    $all := ($dy, $known-distances),

    $mean := avg($all),
    $sq-diff := map(
       function($x) {
          let $res := $x - $mean
            return $x * $x
       },
       $all

    ) return
      mean($sq-diff)
  },
  $sequence
)

Example 4, function argument last (proposal) - 

map($sequence, function($pos as element(pos)) {
    let $y := sin($pos/angle),
    $dy := $y * $pos/angular-distance,
    $all := ($dy, $known-distances),

    $mean := avg($all),
    $sq-diff := map($all, function($x) {
       let $res := $x - $mean return
          $x * $x
    }) return
       mean($sq-diff)
})

Having the function argument last leads to a much more natural way of writing code IMHO and also easier to read code. I can always see very quickly what I am mapping because its the first argument in the map function. Also when using anonymous functions in other functions, its easier to see where they finish and similar to languages such as Scala and Javascript, because you have a final "})"
Comment 1 Adam Retter 2013-04-23 17:46:36 UTC
The example code actually contains a bug - 

"return $x * $x"

should be:

"return $res * $res"
Comment 2 Michael Kay 2013-04-23 22:47:24 UTC
I think it would definitely be a usability improvement to put the sequence argument first and the function last. I do have reservations about making the change at this stage of the game, but on balance I'm in favour; the usability benefits will last much longer than the transition hassle.
Comment 3 Michael Kay 2013-04-29 22:02:40 UTC
There's a classic example in the spec of a function call that would be more readable if arguments are reordered. This:

declare function eg:index-of-node($seq as node()*, $search as node()) as xs:integer* 
{
  fn:for-each-pair(function($node, $index) {
     if($node is $search) then $index else () 
  }, $seq, 1 to fn:count($seq))
}

would become this:

declare function eg:index-of-node($seq as node()*, $search as node()) as xs:integer* 
{
  fn:for-each-pair(
     $seq,
     1 to fn:count($seq),
     function($node, $index) {
        if($node is $search) then $index else () 
     })
}
Comment 4 Liam R E Quin 2013-04-30 00:03:28 UTC
I'm mising why the second approach is more readable than the first in comment 3, although I agree the indenting and layout in the second example is better. But, the following seems equally clear:

declare function
eg:index-of-node($seq as node()*, $search as node())
  as xs:integer* 
{
  fn:for-each-pair(
    function($node, $index) {
      if($node is $search) then $index else () 
    },
    $seq,
    1 to fn:count($seq)
  )
}

It depends on whether you need to know the operation or the operand or both in order to understand - in most cases it's probably both. The function first might feel closer to JavaScript and e.g. jQuery for what it's worth, and apply and map in LISP derivatives feel more natural with the function first.

Overall I'm not so convinced about this change.
Comment 5 Michael Kay 2013-05-07 17:09:50 UTC
The Working Group today accepted the proposal.