Partial applications apply target function when all params are bound

I am aware of the Saxon 9 warning regarding multiple imports of arity.xsl;
I'm going to choose to ignore this, formally in the future.  Each of the
stylesheets is treated like a module.

Michael Kay, Saxon's author, discusses it here:

  http://stackoverflow.com/a/10102298
master
Mike Gerwitz 2014-11-26 23:43:56 -05:00
parent 465ef7e8a5
commit b384e5fad1
4 changed files with 281 additions and 34 deletions

View File

@ -26,16 +26,18 @@
<stylesheet version="2.0"
xmlns="http://www.w3.org/1999/XSL/Transform"
xmlns:xs="http://www.w3.org/2001/XMLSchema"
xmlns:f="http://www.lovullo.com/hoxsl/apply">
xmlns:f="http://www.lovullo.com/hoxsl/apply"
xmlns:_f="http://www.lovullo.com/hoxsl/apply/_priv">
<import href="arity.xsl" />
<!--
Partially apply dynamic function reference FNREF
Partially apply dynamic function reference @var{fnref}
When provided to `f:apply', FNREF will be applied with ARGS as its
beginning argments, followed by any arguments to `f:apply'.
When provided to @code{f:apply}, @var{fnref} will be applied with
@var{args} as its beginning argments, followed by any arguments to
`f:apply'.
Note that you usually do not have to invoke this function directly:
the dynamic function calls will handle currying/partial application
@ -44,6 +46,10 @@
Partially applied functions may continue to be partially applied
until their parameters are exhausted. This can be used to implement
currying.
When destructuring the result of this function, note that the
returned function reference may not match @var{fnref} by reference,
as it may have been modified.
-->
<function name="f:partial" as="item()+">
<param name="fnref" as="item()+" />
@ -72,6 +78,10 @@
select="f:arity( $ref )" />
<choose>
<when test="$argn eq $arity">
<sequence select="_f:apply-partial( $ref, $argout )" />
</when>
<when test="$argn gt $arity">
<apply-templates mode="f:partial-arity-error-hook"
select="$ref">
@ -157,4 +167,36 @@
$argn, ' arguments' ) )" />
</template>
<!--
Apply a partial dynamic function application
This function is called automatically by @code{f:partial} when
partial application would otherwise result in the returning of a
nullary function. @emph{It performs no validations} to ensure the
integrity of the data.}
Just as @code{f:apply}, please note that @emph{up to eight arguments
are supported}. This should be enough.
-->
<function name="_f:apply-partial">
<param name="fnref" as="element(f:ref)" />
<param name="args" as="item()*" />
<variable name="fn" as="element()"
select="$fnref[ 1 ]" />
<!-- just as `f:apply', we support up to 8 arguments -->
<apply-templates select="$fn" mode="f:apply">
<with-param name="arg1" select="$args[ 1 ]" />
<with-param name="arg2" select="$args[ 2 ]" />
<with-param name="arg3" select="$args[ 3 ]" />
<with-param name="arg4" select="$args[ 4 ]" />
<with-param name="arg5" select="$args[ 5 ]" />
<with-param name="arg6" select="$args[ 6 ]" />
<with-param name="arg7" select="$args[ 7 ]" />
<with-param name="arg8" select="$args[ 8 ]" />
</apply-templates>
</function>
</stylesheet>

View File

@ -50,7 +50,7 @@
<!-- technically it applies a template -->
<expect label="applies target function"
test="$x:result[ 1 ] = foo:applied[ @n = 0 ]" />"
test="$x:result[ 1 ] = foo:applied[ @n = 0 ]" />
</scenario>

View File

@ -29,6 +29,9 @@
<!-- SUT -->
<import href="../../src/apply/partial.xsl" />
<!-- numerous templates for arity tests -->
<import href="../apply-test.xsl" />
<!-- generated -->
<import href="partial-test.xsl.apply" />
@ -53,6 +56,7 @@
<param name="y" />
<param name="z" />
<foo:ternary-applied />
<sequence select="$x, $y, $z" />
</function>
</stylesheet>

View File

@ -36,6 +36,10 @@
<foo:b />
<foo:c />
<foo:d />
<foo:e />
<foo:f />
<foo:g />
<foo:h />
</foo:parent>
</variable>
@ -76,41 +80,252 @@
</scenario>
<!-- our implementation can be thought of like currying -->
<scenario label="given arguments for all function params">
<call function="f:partial">
<param name="fnref" select="$fnref" />
<param name="args" select="1, 2, 3" />
<param name="args" select="$args/foo:a,
$args/foo:b,
$args/foo:c" />
</call>
<expect label="returns FNREF as first item in sequence"
test="$x:result[ 1 ] = $fnref" />
<expect label="applies target function"
test="$x:result[ 1 ]
and $x:result[ 1 ] = foo:ternary-applied" />
<!-- we checked previously for argument values; we assume here
that it is working in this case...hopefully that is not
wrong! -->
<expect label="returns all arguments"
test="count( $x:result ) = 4" />
<expect label="applies target function with arguments, by reference"
test="$x:result[ 2 ] is $args/foo:a
and $x:result[ 3 ] is $args/foo:b
and $x:result[ 4 ] is $args/foo:c" />
</scenario>
<!-- partial applications should behave as their own functions -->
<scenario label="partially applying partial application">
<scenario label="partially applying partial application, with
fewer arguments than target function arity">
<call function="f:partial">
<param name="fnref" select="f:partial( $fnref, (1, 2) )" />
<param name="args" select="3" />
<param name="fnref" select="f:partial( $fnref, $args/foo:a )" />
<param name="args" select="$args/foo:b" />
</call>
<expect label="returns FNREF as first item in sequence"
test="$x:result[ 1 ] = $fnref" />
<expect label="returns arguments from both partial applications"
test="count( $x:result ) = 4" />
test="count( $x:result ) = 3" />
<expect label="arguments are ordered with previous applications
first in sequence"
test="$x:result[ 2 ] = 1
and $x:result[ 3 ] = 2
and $x:result[ 4 ] = 3" />
test="$x:result[ 2 ] is $args/foo:a
and $x:result[ 3 ] is $args/foo:b" />
</scenario>
<scenario label="partially applying partial application, with all
arguments">
<call function="f:partial">
<param name="fnref" select="f:partial( $fnref, ($args/foo:a,
$args/foo:b) )" />
<param name="args" select="$args/foo:c" />
</call>
<expect label="applies target function"
test="$x:result[ 1 ]
and $x:result[ 1 ] = foo:ternary-applied" />
<expect label="applies target function with arguments, by reference"
test="$x:result[ 2 ] is $args/foo:a
and $x:result[ 3 ] is $args/foo:b
and $x:result[ 4 ] is $args/foo:c" />
</scenario>
<!-- let the repitition begin (don't we wish we had macros?
...foreshadowing?) -->
<scenario label="completing partial application passes all
arguments to a target">
<scenario label="unary">
<call function="f:partial">
<param name="fnref">
<f:ref arity="1"><foo:fn1 /></f:ref>
</param>
<param name="args" select="$args/foo:a" />
</call>
<expect label="applies target function"
test="$x:result[ 1 ] = foo:applied[ @n = 1 ]" />
<expect label="applies target function with arguments, by reference"
test="$x:result[ 2 ] is $args/foo:a" />
</scenario>
<scenario label="binary">
<call function="f:partial">
<param name="fnref">
<f:ref arity="2"><foo:fn2 /></f:ref>
</param>
<param name="args" select="$args/foo:a,
$args/foo:b" />
</call>
<expect label="applies target function"
test="$x:result[ 1 ] = foo:applied[ @n = 2 ]" />
<expect label="applies target function with arguments, by reference"
test="$x:result[ 2 ] is $args/foo:a
and $x:result[ 3 ] is $args/foo:b" />
</scenario>
<scenario label="ternary">
<call function="f:partial">
<param name="fnref">
<f:ref arity="3"><foo:fn3 /></f:ref>
</param>
<param name="args" select="$args/foo:a,
$args/foo:b,
$args/foo:c" />
</call>
<expect label="applies target function"
test="$x:result[ 1 ] = foo:applied[ @n = 3 ]" />
<expect label="applies target function with arguments, by reference"
test="$x:result[ 2 ] is $args/foo:a
and $x:result[ 3 ] is $args/foo:b
and $x:result[ 4 ] is $args/foo:c" />
</scenario>
<scenario label="4-ary">
<call function="f:partial">
<param name="fnref">
<f:ref arity="4"><foo:fn4 /></f:ref>
</param>
<param name="args" select="$args/foo:a,
$args/foo:b,
$args/foo:c,
$args/foo:d" />
</call>
<expect label="applies target function"
test="$x:result[ 1 ] = foo:applied[ @n = 4 ]" />
<expect label="applies target function with arguments, by reference"
test="$x:result[ 2 ] = $args/foo:a
and $x:result[ 3 ] = $args/foo:b
and $x:result[ 4 ] = $args/foo:c
and $x:result[ 5 ] = $args/foo:d" />
</scenario>
<scenario label="5-ary">
<call function="f:partial">
<param name="fnref">
<f:ref arity="5"><foo:fn5 /></f:ref>
</param>
<param name="args" select="$args/foo:a,
$args/foo:b,
$args/foo:c,
$args/foo:d,
$args/foo:e" />
</call>
<expect label="applies target function"
test="$x:result[ 1 ] = foo:applied[ @n = 5 ]" />
<expect label="applies target function with arguments, by reference"
test="$x:result[ 2 ] = $args/foo:a
and $x:result[ 3 ] = $args/foo:b
and $x:result[ 4 ] = $args/foo:c
and $x:result[ 5 ] = $args/foo:d
and $x:result[ 6 ] = $args/foo:e" />
</scenario>
<scenario label="6-ary">
<call function="f:partial">
<param name="fnref">
<f:ref arity="6"><foo:fn6 /></f:ref>
</param>
<param name="args" select="$args/foo:a,
$args/foo:b,
$args/foo:c,
$args/foo:d,
$args/foo:e,
$args/foo:f" />
</call>
<expect label="applies target function"
test="$x:result[ 1 ] = foo:applied[ @n = 6 ]" />
<expect label="applies target function with arguments, by reference"
test="$x:result[ 2 ] = $args/foo:a
and $x:result[ 3 ] = $args/foo:b
and $x:result[ 4 ] = $args/foo:c
and $x:result[ 5 ] = $args/foo:d
and $x:result[ 6 ] = $args/foo:e
and $x:result[ 7 ] = $args/foo:f" />
</scenario>
<scenario label="7-ary">
<call function="f:partial">
<param name="fnref">
<f:ref arity="7"><foo:fn7 /></f:ref>
</param>
<param name="args" select="$args/foo:a,
$args/foo:b,
$args/foo:c,
$args/foo:d,
$args/foo:e,
$args/foo:f,
$args/foo:g" />
</call>
<expect label="applies target function"
test="$x:result[ 1 ] = foo:applied[ @n = 7 ]" />
<expect label="applies target function with arguments, by reference"
test="$x:result[ 2 ] = $args/foo:a
and $x:result[ 3 ] = $args/foo:b
and $x:result[ 4 ] = $args/foo:c
and $x:result[ 5 ] = $args/foo:d
and $x:result[ 6 ] = $args/foo:e
and $x:result[ 7 ] = $args/foo:f
and $x:result[ 8 ] = $args/foo:g" />
</scenario>
<scenario label="8-ary">
<call function="f:partial">
<param name="fnref">
<f:ref arity="8"><foo:fn8 /></f:ref>
</param>
<param name="args" select="$args/foo:a,
$args/foo:b,
$args/foo:c,
$args/foo:d,
$args/foo:e,
$args/foo:f,
$args/foo:g,
$args/foo:h" />
</call>
<expect label="applies target function"
test="$x:result[ 1 ] = foo:applied[ @n = 8 ]" />
<expect label="applies target function with arguments, by reference"
test="$x:result[ 2 ] = $args/foo:a
and $x:result[ 3 ] = $args/foo:b
and $x:result[ 4 ] = $args/foo:c
and $x:result[ 5 ] = $args/foo:d
and $x:result[ 6 ] = $args/foo:e
and $x:result[ 7 ] = $args/foo:f
and $x:result[ 8 ] = $args/foo:g
and $x:result[ 9 ] = $args/foo:h" />
</scenario>
</scenario>
</scenario>
@ -202,20 +417,6 @@
<expect label="returns true()"
test="$x:result = true()" />
</scenario>
<!-- we still want to consider all arguments to be a partial
application (consider it as producing a nullary
function)—otherwise, the abstraction is broken -->
<scenario label="given arguments for each parameter">
<call function="f:is-partial">
<param name="fnref"
select="f:partial( foo:ternary(), (1, 2, 3) )" />
</call>
<expect label="returns true()"
test="$x:result = true()" />
</scenario>
</scenario>