`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
Mike Gerwitz 2014-12-04 01:14:13 -05:00
commit 4f7bb0fcef
4 changed files with 386 additions and 25 deletions

View File

@ -70,11 +70,17 @@ the output included within a distribution.
### Partial Applications ### Partial Applications
Dynamic function applications using `f:apply` are partially applied if Dynamic function applications using `f:apply` are partially applied if
the number of arguments provided is less than the arity of the target 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 ```xml
<!-- produces a new dynamic function of arity 5 - 3 = 2 --> <!-- produces a new dynamic function of arity 5 - 3 = 2 -->
<variable name="partial" <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 )" /> select="f:apply( my:arity5(), 1, 2, 3 )" />
<!-- consequently, currying is supported --> <!-- consequently, currying is supported -->
@ -84,6 +90,7 @@ function.
<!-- equiv = true() --> <!-- equiv = true() -->
<variable name="equiv" <variable name="equiv"
select="$result select="$result
= f:apply( my:arity5( 1, 2, 3 ), 4, 5 )
= 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 )" /> = my:arity5( 1, 2, 3, 4, 5 )" />
``` ```

View File

@ -32,6 +32,7 @@
xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:xs="http://www.w3.org/2001/XMLSchema"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform" 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"
xmlns:_f="http://www.lovullo.com/hoxsl/apply/_priv"
xmlns:fgen="http://www.lovullo.com/hoxsl/apply/gen" xmlns:fgen="http://www.lovullo.com/hoxsl/apply/gen"
xmlns:out="http://www.lovullo.com/hoxsl/apply/gen/_out" xmlns:out="http://www.lovullo.com/hoxsl/apply/gen/_out"
exclude-result-prefixes="#default fgen"> exclude-result-prefixes="#default fgen">
@ -111,6 +112,37 @@
</template> </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 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 The most important function is the nullary, which returns a dynamic
mode `f:apply' to invoke the associated application template. 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"> <function name="fgen:create-func">
<param name="fnref" as="element(xsl:function)" /> <param name="fnref" as="element(xsl:function)" />
@ -161,15 +198,42 @@
<variable name="local-name" <variable name="local-name"
select="substring-after( $fnref/@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()"> <out:function name="{$name-resolv}" as="element()">
<namespace name="{$ns-prefix}" <namespace name="{$ns-prefix}"
select="$ns" /> select="$ns" />
<variable name="arity"
select="count( $fnref/xsl:param )" />
<sequence select="f:make-ref( $name-resolv, $arity )" /> <sequence select="f:make-ref( $name-resolv, $arity )" />
</out:function> </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> </function>
@ -187,7 +251,7 @@
<param name="ns-prefix" as="xs:string" /> <param name="ns-prefix" as="xs:string" />
<param name="ns" as="xs:anyURI" /> <param name="ns" as="xs:anyURI" />
<variable name="params" <variable name="params" as="element()+"
select="$defn/xsl:param" /> select="$defn/xsl:param" />
<out:template mode="f:apply" <out:template mode="f:apply"
@ -196,27 +260,58 @@
<namespace name="{$ns-prefix}" <namespace name="{$ns-prefix}"
select="$ns" /> select="$ns" />
<for-each select="$params"> <sequence select="_f:param-gen( $params )" />
<xsl:variable name="i"
select="position()" />
<out:param name="arg{$i}"> <variable name="argstr"
<copy-of select="@as" /> select="_f:argstr-gen( $params )" />
</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>
<out:sequence select="{$name-resolv}({$argstr})" /> <out:sequence select="{$name-resolv}({$argstr})" />
</out:template> </out:template>
</function> </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> </stylesheet>

View File

@ -41,4 +41,30 @@
<sequence select="$x - $y" /> <sequence select="$x - $y" />
</function> </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> </stylesheet>

View File

@ -26,6 +26,20 @@
xmlns:foo="http://www.lovullo.com/_junk" xmlns:foo="http://www.lovullo.com/_junk"
stylesheet="apply-gen-test.xsl"> 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 --> <!-- basic case -->
<scenario label="given a unary function"> <scenario label="given a unary function">
<context> <context>
@ -116,6 +130,27 @@
</scenario> </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="given a stylesheet">
<scenario label="with no function elements"> <scenario label="with no function elements">
<context> <context>
@ -218,5 +253,203 @@
select="2" /> select="2" />
</scenario> </scenario>
</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> </scenario>
</description> </description>