Variadic functions and dynamic function calls

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.

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`.

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, *)`.

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.

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.

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.

Michael Kay
Saxonica

Received on Thursday, 10 December 2020 09:58:13 UTC