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" <stylesheet version="2.0"
xmlns="http://www.w3.org/1999/XSL/Transform" xmlns="http://www.w3.org/1999/XSL/Transform"
xmlns:xs="http://www.w3.org/2001/XMLSchema" 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" /> <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 When provided to @code{f:apply}, @var{fnref} will be applied with
beginning argments, followed by any arguments to `f:apply'. @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: Note that you usually do not have to invoke this function directly:
the dynamic function calls will handle currying/partial application the dynamic function calls will handle currying/partial application
@ -44,6 +46,10 @@
Partially applied functions may continue to be partially applied Partially applied functions may continue to be partially applied
until their parameters are exhausted. This can be used to implement until their parameters are exhausted. This can be used to implement
currying. 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()+"> <function name="f:partial" as="item()+">
<param name="fnref" as="item()+" /> <param name="fnref" as="item()+" />
@ -72,6 +78,10 @@
select="f:arity( $ref )" /> select="f:arity( $ref )" />
<choose> <choose>
<when test="$argn eq $arity">
<sequence select="_f:apply-partial( $ref, $argout )" />
</when>
<when test="$argn gt $arity"> <when test="$argn gt $arity">
<apply-templates mode="f:partial-arity-error-hook" <apply-templates mode="f:partial-arity-error-hook"
select="$ref"> select="$ref">
@ -157,4 +167,36 @@
$argn, ' arguments' ) )" /> $argn, ' arguments' ) )" />
</template> </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> </stylesheet>

View File

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

View File

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

View File

@ -36,6 +36,10 @@
<foo:b /> <foo:b />
<foo:c /> <foo:c />
<foo:d /> <foo:d />
<foo:e />
<foo:f />
<foo:g />
<foo:h />
</foo:parent> </foo:parent>
</variable> </variable>
@ -76,41 +80,252 @@
</scenario> </scenario>
<!-- our implementation can be thought of like currying -->
<scenario label="given arguments for all function params"> <scenario label="given arguments for all function params">
<call function="f:partial"> <call function="f:partial">
<param name="fnref" select="$fnref" /> <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> </call>
<expect label="returns FNREF as first item in sequence" <expect label="applies target function"
test="$x:result[ 1 ] = $fnref" /> test="$x:result[ 1 ]
and $x:result[ 1 ] = foo:ternary-applied" />
<!-- we checked previously for argument values; we assume here <expect label="applies target function with arguments, by reference"
that it is working in this case...hopefully that is not test="$x:result[ 2 ] is $args/foo:a
wrong! --> and $x:result[ 3 ] is $args/foo:b
<expect label="returns all arguments" and $x:result[ 4 ] is $args/foo:c" />
test="count( $x:result ) = 4" />
</scenario> </scenario>
<!-- partial applications should behave as their own functions --> <!-- 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"> <call function="f:partial">
<param name="fnref" select="f:partial( $fnref, (1, 2) )" /> <param name="fnref" select="f:partial( $fnref, $args/foo:a )" />
<param name="args" select="3" /> <param name="args" select="$args/foo:b" />
</call> </call>
<expect label="returns FNREF as first item in sequence" <expect label="returns FNREF as first item in sequence"
test="$x:result[ 1 ] = $fnref" /> test="$x:result[ 1 ] = $fnref" />
<expect label="returns arguments from both partial applications" <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 <expect label="arguments are ordered with previous applications
first in sequence" first in sequence"
test="$x:result[ 2 ] = 1 test="$x:result[ 2 ] is $args/foo:a
and $x:result[ 3 ] = 2 and $x:result[ 3 ] is $args/foo:b" />
and $x:result[ 4 ] = 3" /> </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>
</scenario> </scenario>
@ -202,20 +417,6 @@
<expect label="returns true()" <expect label="returns true()"
test="$x:result = true()" /> test="$x:result = true()" />
</scenario> </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> </scenario>