`apply-gen' partial functions now generated
This produces a short-hand partial application (well, it'd be more accurate to call the use of `f:apply' or `f:partial' long-hand, since this is most natural).master
commit
4f7bb0fcef
|
@ -70,11 +70,17 @@ the output included within a distribution.
|
|||
### Partial Applications
|
||||
Dynamic function applications using `f:apply` are partially applied if
|
||||
the number of arguments provided is less than the arity of the target
|
||||
function.
|
||||
function. For convenience, the `apply-gen` stylesheet will also
|
||||
generate functions to perform partial application without the use of
|
||||
`f:apply` for the first call, as in the first example below:
|
||||
|
||||
```xml
|
||||
<!-- produces a new dynamic function of arity 5 - 3 = 2 -->
|
||||
<variable name="partial"
|
||||
select="my:arity5( 1, 2, 3 )" />
|
||||
|
||||
<!-- does the same, the long way -->
|
||||
<variable name="partial-long"
|
||||
select="f:apply( my:arity5(), 1, 2, 3 )" />
|
||||
|
||||
<!-- consequently, currying is supported -->
|
||||
|
@ -84,6 +90,7 @@ function.
|
|||
<!-- equiv = true() -->
|
||||
<variable name="equiv"
|
||||
select="$result
|
||||
= f:apply( my:arity5( 1, 2, 3 ), 4, 5 )
|
||||
= f:apply( my:arity5(), 1, 2, 3, 4, 5 )
|
||||
= my:arity5( 1, 2, 3, 4, 5 )" />
|
||||
```
|
||||
|
|
|
@ -32,6 +32,7 @@
|
|||
xmlns:xs="http://www.w3.org/2001/XMLSchema"
|
||||
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
|
||||
xmlns:f="http://www.lovullo.com/hoxsl/apply"
|
||||
xmlns:_f="http://www.lovullo.com/hoxsl/apply/_priv"
|
||||
xmlns:fgen="http://www.lovullo.com/hoxsl/apply/gen"
|
||||
xmlns:out="http://www.lovullo.com/hoxsl/apply/gen/_out"
|
||||
exclude-result-prefixes="#default fgen">
|
||||
|
@ -111,6 +112,37 @@
|
|||
</template>
|
||||
|
||||
|
||||
<!--
|
||||
Do not process functions that are overloaded on arity
|
||||
|
||||
There are a couple reasons for this: Firstly, overloading is
|
||||
fundamentally incompatible with partial application, because we are unable
|
||||
to determine when a function is fully applied. Secondly, we would have no
|
||||
choice but to generate functions for every arity that does @emph{not}
|
||||
exist. Together, that would yield a very awkward implementation whereby
|
||||
applying N arguments may apply the target function, but N-1 and N+1 may
|
||||
result in a partial application. That is not acceptable.
|
||||
|
||||
To aid in debugging, a comment is output stating that the function was
|
||||
explicitly ignored.
|
||||
-->
|
||||
<template mode="fgen:create"
|
||||
match="xsl:function[
|
||||
@name = root(.)/xsl:*/xsl:function[
|
||||
not( . is current() )
|
||||
]/@name
|
||||
]"
|
||||
priority="5">
|
||||
<comment>
|
||||
<text>No definition generated for overloaded function `</text>
|
||||
<value-of select="@name" />
|
||||
<text>#</text>
|
||||
<value-of select="count( xsl:param )" />
|
||||
<text>'</text>
|
||||
</comment>
|
||||
</template>
|
||||
|
||||
|
||||
<!--
|
||||
Process function definition
|
||||
-->
|
||||
|
@ -147,10 +179,15 @@
|
|||
|
||||
|
||||
<!--
|
||||
Generate nullary function for delayed application
|
||||
Generate dynamic application functions for the application of @var{fnref}.
|
||||
|
||||
This will output a node that can be later applied to a template with
|
||||
mode `f:apply' to invoke the associated application template.
|
||||
The most important function is the nullary, which returns a dynamic
|
||||
function reference that can be later used to apply the function.
|
||||
|
||||
Functions are also generated for partial applications: given a target
|
||||
function @var{f} of arity @var{α}, functions of arity @math{1<x<α} are
|
||||
generated that partially apply @var{f} with @var{x} arguments; this is
|
||||
short-hand for invoking @code{f:apply} or @code{f:partial} directly.
|
||||
-->
|
||||
<function name="fgen:create-func">
|
||||
<param name="fnref" as="element(xsl:function)" />
|
||||
|
@ -161,15 +198,42 @@
|
|||
<variable name="local-name"
|
||||
select="substring-after( $fnref/@name, ':' )" />
|
||||
|
||||
<variable name="arity"
|
||||
select="count( $fnref/xsl:param )" />
|
||||
|
||||
<variable name="params"
|
||||
select="$fnref/xsl:param" />
|
||||
|
||||
<!-- the nullary simply returns a dynamic function reference -->
|
||||
<out:function name="{$name-resolv}" as="element()">
|
||||
<namespace name="{$ns-prefix}"
|
||||
select="$ns" />
|
||||
|
||||
<variable name="arity"
|
||||
select="count( $fnref/xsl:param )" />
|
||||
|
||||
<sequence select="f:make-ref( $name-resolv, $arity )" />
|
||||
</out:function>
|
||||
|
||||
<!-- all others perform partial applications -->
|
||||
<for-each select="1 to ( $arity - 1 )">
|
||||
<variable name="partial-arity"
|
||||
select="." />
|
||||
|
||||
<variable name="subparams"
|
||||
select="subsequence( $params, 1, $partial-arity )" />
|
||||
|
||||
<out:function name="{$name-resolv}" as="item()+">
|
||||
<namespace name="{$ns-prefix}"
|
||||
select="$ns" />
|
||||
|
||||
<variable name="argstr"
|
||||
select="_f:argstr-gen( $subparams )" />
|
||||
|
||||
<sequence select="_f:param-gen( $subparams )" />
|
||||
|
||||
<out:sequence select="f:partial(
|
||||
{$name-resolv}(),
|
||||
({$argstr}) )" />
|
||||
</out:function>
|
||||
</for-each>
|
||||
</function>
|
||||
|
||||
|
||||
|
@ -187,7 +251,7 @@
|
|||
<param name="ns-prefix" as="xs:string" />
|
||||
<param name="ns" as="xs:anyURI" />
|
||||
|
||||
<variable name="params"
|
||||
<variable name="params" as="element()+"
|
||||
select="$defn/xsl:param" />
|
||||
|
||||
<out:template mode="f:apply"
|
||||
|
@ -196,27 +260,58 @@
|
|||
<namespace name="{$ns-prefix}"
|
||||
select="$ns" />
|
||||
|
||||
<for-each select="$params">
|
||||
<xsl:variable name="i"
|
||||
select="position()" />
|
||||
<sequence select="_f:param-gen( $params )" />
|
||||
|
||||
<out:param name="arg{$i}">
|
||||
<copy-of select="@as" />
|
||||
</out:param>
|
||||
</for-each>
|
||||
|
||||
<variable name="argstr">
|
||||
<for-each select="$params">
|
||||
<if test="position() gt 1">
|
||||
<text>, </text>
|
||||
</if>
|
||||
<text>$arg</text>
|
||||
<value-of select="position()" />
|
||||
</for-each>
|
||||
</variable>
|
||||
<variable name="argstr"
|
||||
select="_f:argstr-gen( $params )" />
|
||||
|
||||
<out:sequence select="{$name-resolv}({$argstr})" />
|
||||
</out:template>
|
||||
</function>
|
||||
|
||||
|
||||
<!--
|
||||
Given a sequence of XSLT function parameters @var{params}, generate
|
||||
enumerated argument @code{<param>}s.
|
||||
|
||||
This is to be used in conjunction with @code{_f:argstr-gen}.
|
||||
-->
|
||||
<function name="_f:param-gen" as="element()+">
|
||||
<param name="params" as="element()+" />
|
||||
|
||||
<for-each select="$params">
|
||||
<xsl:variable name="i"
|
||||
select="position()" />
|
||||
|
||||
<out:param name="arg{$i}">
|
||||
<copy-of select="@as" />
|
||||
</out:param>
|
||||
</for-each>
|
||||
</function>
|
||||
|
||||
|
||||
<!--
|
||||
Given a sequence of XSLT function parameters @var{params}, generate
|
||||
a string that, when used in an XPath, represents a list of arguments of a
|
||||
function application.
|
||||
|
||||
This is to be used in conjunction with @code{_f:param-gen}.
|
||||
-->
|
||||
<function name="_f:argstr-gen" as="xs:string">
|
||||
<param name="params" as="element()+" />
|
||||
|
||||
<xsl:variable name="argstr">
|
||||
<for-each select="$params">
|
||||
<if test="position() gt 1">
|
||||
<text>, </text>
|
||||
</if>
|
||||
<text>$arg</text>
|
||||
<value-of select="position()" />
|
||||
</for-each>
|
||||
</xsl:variable>
|
||||
|
||||
<!-- force to string -->
|
||||
<value-of select="string( $argstr )" />
|
||||
</function>
|
||||
|
||||
</stylesheet>
|
||||
|
|
|
@ -41,4 +41,30 @@
|
|||
|
||||
<sequence select="$x - $y" />
|
||||
</function>
|
||||
|
||||
|
||||
<!-- overloaded function, for which we cannot generate anything -->
|
||||
<function name="foo:overloaded">
|
||||
<param name="a" />
|
||||
</function>
|
||||
<function name="foo:overloaded">
|
||||
<param name="a" />
|
||||
<param name="b" />
|
||||
</function>
|
||||
|
||||
|
||||
<!-- large number of arguments to test partial application -->
|
||||
<function name="foo:eight" as="item()+">
|
||||
<param name="arg1" />
|
||||
<param name="arg2" />
|
||||
<param name="arg3" />
|
||||
<param name="arg4" />
|
||||
<param name="arg5" />
|
||||
<param name="arg6" />
|
||||
<param name="arg7" />
|
||||
<param name="arg8" />
|
||||
|
||||
<sequence select="$arg1, $arg2, $arg3, $arg4,
|
||||
$arg5, $arg6, $arg7, $arg8" />
|
||||
</function>
|
||||
</stylesheet>
|
||||
|
|
|
@ -26,6 +26,20 @@
|
|||
xmlns:foo="http://www.lovullo.com/_junk"
|
||||
stylesheet="apply-gen-test.xsl">
|
||||
|
||||
<variable name="args">
|
||||
<foo:args>
|
||||
<foo:arg1 />
|
||||
<foo:arg2 />
|
||||
<foo:arg3 />
|
||||
<foo:arg4 />
|
||||
<foo:arg5 />
|
||||
<foo:arg6 />
|
||||
<foo:arg7 />
|
||||
<foo:arg8 />
|
||||
</foo:args>
|
||||
</variable>
|
||||
|
||||
|
||||
<!-- basic case -->
|
||||
<scenario label="given a unary function">
|
||||
<context>
|
||||
|
@ -116,6 +130,27 @@
|
|||
</scenario>
|
||||
|
||||
|
||||
<!-- see SUT for rationale -->
|
||||
<scenario label="given an arity-overloaded function">
|
||||
<context>
|
||||
<xsl:stylesheet>
|
||||
<xsl:function name="foo:overloaded">
|
||||
<xsl:param name="a" />
|
||||
</xsl:function>
|
||||
|
||||
<xsl:function name="foo:overloaded">
|
||||
<xsl:param name="a" />
|
||||
<xsl:param name="b" />
|
||||
</xsl:function>
|
||||
</xsl:stylesheet>
|
||||
</context>
|
||||
|
||||
<expect label="yields no functions of that name"
|
||||
test="not( xsl:stylesheet/xsl:function[
|
||||
@name='foo:overloaded' ] )" />
|
||||
</scenario>
|
||||
|
||||
|
||||
<scenario label="given a stylesheet">
|
||||
<scenario label="with no function elements">
|
||||
<context>
|
||||
|
@ -218,5 +253,203 @@
|
|||
select="2" />
|
||||
</scenario>
|
||||
</scenario>
|
||||
|
||||
|
||||
<!-- more duplication...we support up to eight arguments, so we'll
|
||||
have to test the generation of 1..7 (nullary is already
|
||||
tested and 8 results in a full application) -->
|
||||
<scenario label="with partial functions of 8-ary target, given">
|
||||
<variable name="qname"
|
||||
select="QName( 'http://www.lovullo.com/_junk',
|
||||
'foo:eight' )" />
|
||||
|
||||
<scenario label="one argument">
|
||||
<call function="foo:eight">
|
||||
<param name="arg1" select="$args/foo:arg1" />
|
||||
</call>
|
||||
|
||||
<expect label="returns partially applied function"
|
||||
test="f:is-partial( $x:result )" />
|
||||
|
||||
<expect label="references the target function"
|
||||
test="f:QName( $x:result ) = $qname" />
|
||||
|
||||
<expect label="partially applies one argument"
|
||||
test="f:arity( $x:result ) = 7" />
|
||||
|
||||
<variable name="result-args" as="item()+"
|
||||
select="f:args( $x:result )" />
|
||||
<expect label="argument is partially applied by reference"
|
||||
test="$result-args[ 1 ] is $args/foo:arg1" />
|
||||
</scenario>
|
||||
|
||||
|
||||
<scenario label="two arguments">
|
||||
<call function="foo:eight">
|
||||
<param name="arg1" select="$args/foo:arg1" />
|
||||
<param name="arg2" select="$args/foo:arg2" />
|
||||
</call>
|
||||
|
||||
<expect label="returns partially applied function"
|
||||
test="f:is-partial( $x:result )" />
|
||||
|
||||
<expect label="references the target function"
|
||||
test="f:QName( $x:result ) = $qname" />
|
||||
|
||||
<expect label="partially applies one argument"
|
||||
test="f:arity( $x:result ) = 6" />
|
||||
|
||||
<variable name="result-args" as="item()+"
|
||||
select="f:args( $x:result )" />
|
||||
<expect label="argument is partially applied by reference"
|
||||
test="$result-args[ 1 ] is $args/foo:arg1
|
||||
and $result-args[ 2 ] is $args/foo:arg2" />
|
||||
</scenario>
|
||||
|
||||
|
||||
<scenario label="three arguments">
|
||||
<call function="foo:eight">
|
||||
<param name="arg1" select="$args/foo:arg1" />
|
||||
<param name="arg2" select="$args/foo:arg2" />
|
||||
<param name="arg3" select="$args/foo:arg3" />
|
||||
</call>
|
||||
|
||||
<expect label="returns partially applied function"
|
||||
test="f:is-partial( $x:result )" />
|
||||
|
||||
<expect label="references the target function"
|
||||
test="f:QName( $x:result ) = $qname" />
|
||||
|
||||
<expect label="partially applies one argument"
|
||||
test="f:arity( $x:result ) = 5" />
|
||||
|
||||
<variable name="result-args" as="item()+"
|
||||
select="f:args( $x:result )" />
|
||||
<expect label="argument is partially applied by reference"
|
||||
test="$result-args[ 1 ] is $args/foo:arg1
|
||||
and $result-args[ 2 ] is $args/foo:arg2
|
||||
and $result-args[ 3 ] is $args/foo:arg3" />
|
||||
</scenario>
|
||||
|
||||
|
||||
<scenario label="four arguments">
|
||||
<call function="foo:eight">
|
||||
<param name="arg1" select="$args/foo:arg1" />
|
||||
<param name="arg2" select="$args/foo:arg2" />
|
||||
<param name="arg3" select="$args/foo:arg3" />
|
||||
<param name="arg4" select="$args/foo:arg4" />
|
||||
</call>
|
||||
|
||||
<expect label="returns partially applied function"
|
||||
test="f:is-partial( $x:result )" />
|
||||
|
||||
<expect label="references the target function"
|
||||
test="f:QName( $x:result ) = $qname" />
|
||||
|
||||
<expect label="partially applies one argument"
|
||||
test="f:arity( $x:result ) = 4" />
|
||||
|
||||
<variable name="result-args" as="item()+"
|
||||
select="f:args( $x:result )" />
|
||||
<expect label="argument is partially applied by reference"
|
||||
test="$result-args[ 1 ] is $args/foo:arg1
|
||||
and $result-args[ 2 ] is $args/foo:arg2
|
||||
and $result-args[ 3 ] is $args/foo:arg3
|
||||
and $result-args[ 4 ] is $args/foo:arg4" />
|
||||
</scenario>
|
||||
|
||||
|
||||
<scenario label="five arguments">
|
||||
<call function="foo:eight">
|
||||
<param name="arg1" select="$args/foo:arg1" />
|
||||
<param name="arg2" select="$args/foo:arg2" />
|
||||
<param name="arg3" select="$args/foo:arg3" />
|
||||
<param name="arg4" select="$args/foo:arg4" />
|
||||
<param name="arg5" select="$args/foo:arg5" />
|
||||
</call>
|
||||
|
||||
<expect label="returns partially applied function"
|
||||
test="f:is-partial( $x:result )" />
|
||||
|
||||
<expect label="references the target function"
|
||||
test="f:QName( $x:result ) = $qname" />
|
||||
|
||||
<expect label="partially applies one argument"
|
||||
test="f:arity( $x:result ) = 3" />
|
||||
|
||||
<variable name="result-args" as="item()+"
|
||||
select="f:args( $x:result )" />
|
||||
<expect label="argument is partially applied by reference"
|
||||
test="$result-args[ 1 ] is $args/foo:arg1
|
||||
and $result-args[ 2 ] is $args/foo:arg2
|
||||
and $result-args[ 3 ] is $args/foo:arg3
|
||||
and $result-args[ 4 ] is $args/foo:arg4
|
||||
and $result-args[ 5 ] is $args/foo:arg5" />
|
||||
</scenario>
|
||||
|
||||
|
||||
<scenario label="six arguments">
|
||||
<call function="foo:eight">
|
||||
<param name="arg1" select="$args/foo:arg1" />
|
||||
<param name="arg2" select="$args/foo:arg2" />
|
||||
<param name="arg3" select="$args/foo:arg3" />
|
||||
<param name="arg4" select="$args/foo:arg4" />
|
||||
<param name="arg5" select="$args/foo:arg5" />
|
||||
<param name="arg6" select="$args/foo:arg6" />
|
||||
</call>
|
||||
|
||||
<expect label="returns partially applied function"
|
||||
test="f:is-partial( $x:result )" />
|
||||
|
||||
<expect label="references the target function"
|
||||
test="f:QName( $x:result ) = $qname" />
|
||||
|
||||
<expect label="partially applies one argument"
|
||||
test="f:arity( $x:result ) = 2" />
|
||||
|
||||
<variable name="result-args" as="item()+"
|
||||
select="f:args( $x:result )" />
|
||||
<expect label="argument is partially applied by reference"
|
||||
test="$result-args[ 1 ] is $args/foo:arg1
|
||||
and $result-args[ 2 ] is $args/foo:arg2
|
||||
and $result-args[ 3 ] is $args/foo:arg3
|
||||
and $result-args[ 4 ] is $args/foo:arg4
|
||||
and $result-args[ 5 ] is $args/foo:arg5
|
||||
and $result-args[ 6 ] is $args/foo:arg6" />
|
||||
</scenario>
|
||||
|
||||
|
||||
<scenario label="seven arguments">
|
||||
<call function="foo:eight">
|
||||
<param name="arg1" select="$args/foo:arg1" />
|
||||
<param name="arg2" select="$args/foo:arg2" />
|
||||
<param name="arg3" select="$args/foo:arg3" />
|
||||
<param name="arg4" select="$args/foo:arg4" />
|
||||
<param name="arg5" select="$args/foo:arg5" />
|
||||
<param name="arg6" select="$args/foo:arg6" />
|
||||
<param name="arg7" select="$args/foo:arg7" />
|
||||
</call>
|
||||
|
||||
<expect label="returns partially applied function"
|
||||
test="f:is-partial( $x:result )" />
|
||||
|
||||
<expect label="references the target function"
|
||||
test="f:QName( $x:result ) = $qname" />
|
||||
|
||||
<expect label="partially applies one argument"
|
||||
test="f:arity( $x:result ) = 1" />
|
||||
|
||||
<variable name="result-args" as="item()+"
|
||||
select="f:args( $x:result )" />
|
||||
<expect label="argument is partially applied by reference"
|
||||
test="$result-args[ 1 ] is $args/foo:arg1
|
||||
and $result-args[ 2 ] is $args/foo:arg2
|
||||
and $result-args[ 3 ] is $args/foo:arg3
|
||||
and $result-args[ 4 ] is $args/foo:arg4
|
||||
and $result-args[ 5 ] is $args/foo:arg5
|
||||
and $result-args[ 6 ] is $args/foo:arg6
|
||||
and $result-args[ 7 ] is $args/foo:arg7" />
|
||||
</scenario>
|
||||
</scenario>
|
||||
</scenario>
|
||||
</description>
|
||||
|
|
Loading…
Reference in New Issue