Re: Proposal for variadic functions

On Tue, 8 Dec 2020 at 08:57, Michael Kay <mike@saxonica.com> wrote:

> Here is an attempt to consolidate a proposal for variadic functions that
> combines all the ideas and requirements that have been expressed.
>
> I would worry that the proposal is over-complex, if it weren't that Python
> seems to offer all these options and users seem quite happy with it.
>
> Note: I use the word parameters to refer to things in the function
> declaration, and arguments to refer to things in the function call. The
> distinction is important.
>
> TODO: I haven't tried yet to define rules for partial application.
>
> A. Some general rules about variadic functions.
>
> If a function is variadic, then the number of arguments in a function call
> can differ from the number of parameters in the function declaration.
>
> If a function is variadic, then its name must be unique within the static
> context.
>
> A variadic function can be identified by a function reference of the form
> name#*. fn:function-arity applied to a variadic function returns ();
> fn:function-lookup and fn:function-available accept a second argument of ()
> to identify a variadic function.
>
> A function call contains zero or more positional arguments followed by
> zero or more keyword arguments. Keyword arguments can also be used when
> calling non-variadic functions, though the number of arguments in that case
> is fixed.
>
> We identify three kinds of variadic function, which I will call
> bounded-variadic, array-variadic, and map-variadic.
>
> B. Bounded-variadic functions.
>
> In a bounded-variadic function, one or more of the parameters are declared
> as optional, with a default value.
>
> The number of supplied arguments in a function call must be >= the number
> of non-optional parameters, and <= the total number of declared parameters.
> (Hence the name, bounded).
>
> A positional argument is bound to the parameter at the corresponding
> position. A keyword argument is bound to the parameter with the
> corresponding name. It is an error if two arguments are bound to the same
> parameter, or if no argument is bound to a required parameter, or if too
> few or too many arguments are supplied.
>
> TODO: define the rules for evaluation of default values, e.g. can they be
> context dependent (if so, what is the context), can the default value of
> one argument depend on the supplied values of other arguments, etc.
>
> Typical example: format-date(). This can now be redefined as a
> bounded-variadic function with 5 declared parameters of which the last
> three are optional. Calls with two positional arguments and five positional
> arguments remain valid; it also becomes possible to supply 3 or 4
> positional arguments, and to supply any of the arguments (including the
> required arguments) using keywords.
>

+1

I like the concept of bounded-variadic functions as the mechanism to
support default arguments. It fits in with the rest of the proposal, and
works well with the logic for array-variadic functions.

For compatibility, format-date#2 and format-date#5 remain available as
> references to virtual fixed-arity functions that map directly to the
> variadic function.
>

-1

My concern here is introducing functionality that is not defined as part of
the language in mapping the function references with the special casing
logic to create virtual-arity functions, so we end up in the same situation
as the current variadic arguments for concat, and for context-sensitive
functions. It makes it difficult for tool vendors to support (especially if
the functions the special casing applies to varies between vendors), and
has the logic of only applying to the standard library and not other
functions. For example, MarkLogic has a lot of functions that have default
arguments, so it would be helpful if they could support this without having
to special case those functions as well.

The way I've approached this for array-variadic functions is to have the
function arity be a (min bound, max bound) range where a function reference
or call matches if the argument arity (a scalar value) is within the
function parameter arity. For a non-variadic function the min and max
bounds are the same. I could easily see using that same mechanism to
support bounded-variadic functions. It also means that there is no special
case logic to define fixed-arity virtual functions, and would allow
vendor-specific and user-defined functions to be rewritten to make use of
default arguments while retaining compatibility with any references bound
to them.


> C. Array-variadic functions.
>
> In an array-variadic function, the last parameter has an array type. If
> the last parameter is at position N, then the supplied arguments at
> position N, N+1. N+2, etc, are combined into an array, and the array is
> passed as the value of the last parameter. The caller also has the option
> of passing an actual array as the value of the argument; this is done by
> using a keyword argument. The array has an implicit default of [].
>
> The number of supplied arguments must be >= the number of required
> parameters (where the array argument is not counted as a required
> parameter: it effectively defaults to an empty array). There is no upper
> bound on the number of arguments.
>
> Typical example: concat(). All existing calls of concat remain valid, as
> do function references such as concat#17. But it now becomes possible to
> supply a single argument as an array, by keyword.
>

+1 with my caveat around not having function references be special cased
for variadic functions.


> D. Map-variadic functions.
>
> In a map-variadic function, the last parameter has a map type. All keyword
> arguments in the function call are combined into a map, which is supplied
> as the value of this last parameter. Values for parameters other than the
> last can only be supplied positionally. It is also possible for the
> function call to supply the map as a single argument, in which case it must
> be supplied as a positional argument. The map has an implicit default of
> map{}.
>
> The number of supplied arguments must be >= the number of required
> parameters (where the map argument is not counted as a required parameter:
> it effectively defaults to an empty map). There is no upper bound on the
> number of arguments.
>
> Typical example: serialize(). You can supply any keywords you like; the
> function will ignore any that it doesn't recognise.
>

+1

This should be supported for record types as well, in which case the number
of arguments would be bounded and you would be able to have validation on
the additional arguments. It will also allow tool vendors to support better
type checking and auto-complete support for those functions. If you want it
to be open-ended, you could use an open-ended/extensible record type.

Kind regards,
Reece


> Michael Kay
> Saxonica
>

Received on Tuesday, 8 December 2020 12:33:35 UTC