- From: Reece Dunn <msclrhd@googlemail.com>
- Date: Thu, 10 Dec 2020 13:01:03 +0000
- To: Michael Kay <mike@saxonica.com>
- Cc: public-xslt-40@w3.org
- Message-ID: <CAGdtn27iESphwVUgSrQP8_VW43WDh7h5YBypNX6m9NnP+6qUVQ@mail.gmail.com>
On Thu, 10 Dec 2020 at 09:58, Michael Kay <mike@saxonica.com> wrote: > In my proposal for variadic functions, which has been reasonably well > received subject to some details, I largely ignored the question of dynamic > function calls. > > Starting point: if dynamic function calls can use keyword arguments that > map to parameter names, then the keywords need to be included in the > function signature, and the supplied function must then use the same > keywords in its declaration as the required function. > > This strikes me as something likely to lead to immense complexity and > considerable usability problems. It's a pain if you can't supply a function > to an interface if the declaration uses the wrong parameter names. It also > makes the rules for definining function substitutability (subsumption) even > more complicated than they already are. I would prefer not to go there. > Agreed. > So what's the alternative? If keywords aren't part of the function > signature, then I think dynamic function calls can't use parameter names as > keywords. What are the implications of this? > > Without any change to the way function signatures are defined, it's still > possible to supply a "bounded-variadic" function where the required type is > non-variadic. For example if the required type is a predicate function -- > `function(item()) as xs:boolean` -- then it's possible to supply the > bounded variadic function > > function increment ($x as xs:integer, $p as xs:integer := 1) {$x+$p} > > because a call on increment(12) is perfectly acceptable and meaningful and > returns 13. In effect, the bounded-variadic function has a non-variadic > "personality", it is substitutable for the required function type. It is > also substitutable for the type `function(xs:integer, xs:integer) as > xs:boolean`. > +1 This is in line with my thinking about function references in the other thread. -- Specifically where a function reference with the same arity as the number of parameters on the bounded-variadic function creates a non-variadic reference to the function. > What about map-variadic and array-variadic functions? > > With a map-variadic function, keyword arguments don't map to parameter > names, they map to entries in the map that is constructed from the call and > passed as the last argument. So keyword arguments are still possible, > provided the function signature identifies the function as being > map-variadic. Rather than adding a "map-variadic" property to the function > signature, we could treat the function as being map-variadic if and only if > the declared type of the last argument is a record type e.g. `record(a as > xs:integer, b as xs:boolean, *)`. > I think it makes sense to support both map and record types for map-variadic functions. The map-variadic functions are also the trickiest variadic type to define the semantics for. By my thinking on function references, the function signature it matches for subsumption rules the last parameter would be the map/record the function was declared as and follow those rules accordingly -- that is, mapping parameters of a function signature to keyword arguments is not supported. The question then is how the map/record behaves when calling the function. If we are following that the variadic nature applies at the call site instead of the declaration site (i.e. building the array/map/sequence applies at the point at which the function is called), then I don't think anything else is needed -- the function will be able to be called using keyword arguments if the last parameter is a map or record. This would also provide consistency with things like: declare function local:test($a as xs:int, $b as map(*)) {}; let $f := local:test#2 (: or an inline function expression, e.g. function ($a as xs:int, $b as map(*)) {} :) return $f(2, alpha := 1, beta := 2) > > With an array-variadic function, in the current state of the proposal, all > arguments are supplied positionally, so keywords aren't a problem, except > for the one case where you want to supply the array argument explicitly as > an array. > My thinking is that the last argument to an array-variadic function is either an array, or the item-type it is an array over. That is, a function reference name#arity where arity is the number of parameters in the array-variadic function is a union-of(array(T), T). For function matching rules, this would mean that the last parameter can either be an array(T) or T. This way, both function references and function matching/subsumption work in the same way (behaving in this instance like sequences). > I think if we switched to using a sequence rather than an array for this > kind of function, we could take advantage of the fact that a single item is > equivalent to a sequence of length 1. So if we defined a function > `product(xs:double...) as xs:double` as a function that takes a sequence of > xs:doubles and returns their product, then we could allow both > product(1,2,3,4) and product((1,2,3,4)) as calls. We could either (a) add > the "variadicity" of the function to the function signature, allowing > dynamic calls to take either form, or (b) require the function signature to > be `function(xs:double*) as xs:double` and allow only the second form in a > dynamic call, or (c) (more radically) treat function signatures as > sequence-variadic by default, so if the dynamic function has arity 3, and > the function call has 5 arguments, then we combine the values of arguments > 3, 4, and 5 together into a sequence supplied as the value of parameter 3. > I think it makes sense to keep array-variadic functions, as there are cases (such as building JSON objects/arrays) where you would want to preserve the sequences and not have them flattened. I tentatively like the concept of sequence-variadic functions from the perspective of having symmetry across the different types w.r.t. variadic function support. For consistency with the proposal as defined, a sequence-variadic function would need to work as option (c), however that could lead to pitfalls from a user perspective and also lead to potential bugs if given f(xs:int*, xs:long*) and passing f(1, 2, 3) by mistake and how would it work if an f(xs:int*) function was also defined (where the expectation would be it would be a bounded-variadic function `f(xs:int*, xs:long* := ())`). If we require sequence-variadic functions to use `...` that would be better, but then results in an inconsistency in how variadic functions work. Given the requirement to make this compatible with existing map-based options I'm happy with using `ItemType...` as a compromise syntax specifically for sequence-variadic functions (which maps to the `ItemType*` sequence type in the function definition). -- This way, a function reference or function signature with the same arity as the number of parameters in the function will have a last parameter as a sequence type, supporting passing a sequence of values or a single item, but not additional parameters. > So my current thinking is along the following lines: > > (A) Make minimal change to function signatures or to the rules for > subsumption of function types: in particular, don't add parameter names to > the function type. > > (B) A bounded-arity function F can be supplied where the required type has > a fixed arity N provided that a static call on F with N arguments is > allowed; a dynamic call with N positional arguments is handled in the same > way as a static call with N positional arguments; a dynamic call with > keyword arguments is not allowed. > > (C) If the expected type of a function has arity N, then in a dynamic > function call with M arguments (M >= N-1), the values of supplied arguments > in positions N, N+1, N+2, etc are sequence-concatenated together and > supplied as the value for argument N in the function signature. > Alternative: allow this only if the occurrence indicator of the last > parameter in the function signature is "...". > > (D) Keyword arguments in a dynamic function call are allowed only if the > last argument in the function signature is a record type (which may or may > not be an extensible record type); it is possible to supply a value for > this argument either as a single positional argument evaluating to a map, > or as a set of keyword arguments matched against the names in the record > type. > +1 These all look reasonable. As noted in my comments above, I prefer (C) to only apply when the occurrence indicator of the last parameter is `ItemType...`. (E) If the expected type of a function has arity N and the last argument is an array, then in a dynamic function call with M arguments (M >= N), the values of supplied arguments in positions N, N+1, N+2, etc are combined into an array of size M-N with the of [aN, aN+1, ..., aM] and supplied as the value for argument N in the function signature. (F) If the expected type of a function has arity N and the last argument is an array, then in a dynamic function call with N arguments where the last argument is an item type that is a subtype of the item type associated with the array, a single-item array with argument N as its value is passed as the value to parameter N. Kind regards, Reece > Michael Kay > Saxonica > > > > > > >
Received on Thursday, 10 December 2020 13:01:32 UTC