[Bug 26585] New: Proposal: fn:apply

https://www.w3.org/Bugs/Public/show_bug.cgi?id=26585

            Bug ID: 26585
           Summary: Proposal: fn:apply
           Product: XPath / XQuery / XSLT
           Version: Working drafts
          Hardware: PC
                OS: All
            Status: NEW
          Severity: normal
          Priority: P2
         Component: XQuery 3.1
          Assignee: jonathan.robie@gmail.com
          Reporter: marc.van.grootel@gmail.com
        QA Contact: public-qt-comments@w3.org

XQuery 3.0 introduced functions that are derived from functional programming.
Function items can be passed as arguments to functions like fn:fold-left().
Together they allow a more functional style of coding. Most of these functions
can be used to perform map, filter, reduce on sequences.

However, one important function that is missing and for which I do not know a
reasonable workaround for is "apply" [1]. The wikipedia page shows that many
languages have ways to achieve this and Clojure has a function with the same
name [2].

In XQuery we can bind a variable to a function item. But when we want to use
it we are limited to one argument.

  $f = function my-add($a,$b,$c) { $a + $b + $c }
  $args = (1,2,3)

  $f($args) == $f((1,2,3))

If we had an apply function this would do it.

  fn:apply($f, $args) == $f(1,2,3)

Of course we could rewrite the function my-add to something like:

  $f = function my-add($seq) { sum($seq) }

  $f($args) == 6

But this is only practical for a limited number of cases and the function body
would need to unpack the sequence, and we also lose the type checking.

This limitation also shows with the proposed arrow operator (=>).

Someone coming from a language like Clojure, Lisp or Scheme would interpret the
following

  (1,2,3) => $f

to mean 

  $f(1,2,3)

but in XQuery it would mean

  $f((1,2,3))

The arrow operator is similar to Clojure's threading operator (->) [3].

Clojure:

  (-> (1 2 3) f1 f2) == (f2 (f1 1 2 3))

Note that in Clojure -> is a macro which re-writes the form before the
compiler will see it. But I digress.

XQuery:

  (1,2,3) => $f1 => $f2 

  $f2($f1((1,2,3))

Maybe this is a consequence of the fact that sequences cannot be nested, and
are effectively flattened.

I cannot oversee all the consequences but like a map may contain other maps
arrays can be nested so they could be used for "apply" semantics.

Currently we have:

  (1,2,3) => $f

  $f((1,2,3))

With array apply semantics:

  [1,2,3] => $f1 => $f2

  == $f2($f1(1,2,3))

  [(1,2),(3,4)] => $f1

  == $f1((1,2),(3,4))

  == fn:apply($f1, [(1,2),(3,4)])

On the surface this looks like an elegant solution to me and is closer to
the apply semantics in other languages.

A particular use case for fn:apply arose when I tried implementing Clojure Ring
and Compojure libraries in XQuery. These are comparable to WSGI in Python and
Rack in Ruby.

Below I simplified things but I hope it illustrates a concrete use case.

I have a function for defining a routing handler which maps an HTTP GET request
to
a handler function. The def-route() signature is

  function(
    $method as xs:string,
    $url-template as xs:string,
    $params as array(*),
    $handler as function(*)
  ) as function(map(*)) as map(*)

This means that a route handler function is something that takes a request
map as input and produces a response map as output.

  $handler = 
      function($name, $age) { 
        'Person: ' || $name || ' (age ' || $age ')' 
      }

  $route = def-route('GET', '/person/{name}/{age}', ['name', 'age'], $handler)

  $route(request('GET', '/person/Joe/10'))

When a GET request comes in on '/person/Joe/10'. The route-handler $route
matches and the 'name' and 'age' parameters are parsed from the URL and added
to
the request map. The third argument is an array that specifies which keys need
to be taken from the request map and builds the $params array: ['Joe', '10'].
Finally it can invoke the $handler using

  fn:apply($handler, $params)

or

  $params => $handler

  == ['Joe', '10'] => $handler

This results in a response('Person: Joe (age 10)') which is then rendered to
the
browser.

Note that the handler does not need to know where to find the parameters
because
this is set up by the $route handler thus achieving a clean separation of
concerns and the possibility of using $handler in other situations because
it is not tied to the plumbing/routing logic.

As I said earlier, I may not be able to oversee all the consequences as I'm not
super intimate with all the specs involved. Or, my pseudo code is confusing.
Mea
culpa.

--Marc

[1] http://en.wikipedia.org/wiki/Apply
[2] http://clojuredocs.org/clojure_core/clojure.core/apply
[3] http://clojuredocs.org/clojure_core/clojure.core/-%3E

-- 
You are receiving this mail because:
You are the QA Contact for the bug.

Received on Thursday, 14 August 2014 19:32:57 UTC