Proposal for variadic functions

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.

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.

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.

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.

Michael Kay
Saxonica

Received on Tuesday, 8 December 2020 08:57:31 UTC