- From: Reece Dunn <msclrhd@googlemail.com>
- Date: Sun, 13 Dec 2020 22:14:23 +0000
- To: Dimitre Novatchev <dnovatchev@gmail.com>
- Cc: Michael Kay <mike@saxonica.com>, public-xslt-40@w3.org
- Message-ID: <CAGdtn26af0bX6ZGAUi8GT77pXFsTVkVUTafkwRt2vxqQ+1y8MQ@mail.gmail.com>
On Sun, 13 Dec 2020 at 21:07, Dimitre Novatchev <dnovatchev@gmail.com> wrote: > > >> >> >>> *Quote*: >>> >>> "%variadic("sequence") indicates that the function is >>> sequence-variadic. A sequence-variadic function declares one or more parameters, >>> of which the last typically has an occurrence indicator of * or + to >>> indicate that a sequence may be supplied. >>> >>> *Questions*: >>> 1. Why sequence-variadic and not array-variadic? >>> >>> If an array is used, then it can hold for example: >>> [1, (), 2, 3] >>> >>> and all 4 elements of the array will be accessible from the function >>> call, not just 3 as in the case when a sequence is passed: >>> (1, (), 2, 3) >>> >>> Thus having array-variadic functions (function calls) is more precise >>> and expressive, as shown in this example. >>> >> >> In the "Variadic functions and dynamic function calls" thread, Michael >> Kay explained that this is due to `[]` being ambiguous -- is it a single >> argument to the variadic version and thus passed as `[ [] ]`, or is it the >> value of the parameter itself? In that thread, I've proposed a >> fn:function-arguments function as a way to get the array version of the >> parameters if required: >> >> fn:function-arguments($from as xs:integer := 1, $to as xs:integer? := >> ()) as array(*) >> (: This function is ·deterministic·, ·context-dependent·, and >> ·focus-independent·. :) >> > > Actually, there isn't any ambiguity: > > [ [] ] is an array with a single item in it, which is the empty array > (arrays in this respect are different from sequences and do not > remove/flatten their empty items), > while [] is the empty array (containing 0 items). > The ambiguity happens at the point at which the function is called. If the last argument is an array, then passing a single array value could be interpreted as passing that array to the function (`[]`) or applying the array-valiadic logic and constructing an array of that parameter and then passing that (`[ [] ]`) to the array parameter. With sequences (as noted by Michael Kay), they don't have this issue, as `T` and `(T)` are equivalent. Note: There is an equivalent potential issue with map-variadic functions. Given a function `f($options as map(*))`, how will the function call `f($options := map {})` be interpreted? -- Is that passing an empty map to the map parameter (via the rule for naming parameters), or constructing a new `map { "options": map {} }` object (using the map-variadic rules)? > >> >> 2. If the only reason is that fn:concat() cannot be expressed/called as >>> an array-variadic function, can we have both: array-variadic and >>> sequence-variadic (the latter just for fn:concat() ) defined? >>> >> >> It can be expressed as either, however the value flattening for fn:concat >> is more intuitive when defined as a sequence-variadic function. See the >> explanation above. >> > > As per the conclusion above, that actually there is no ambiguity, a call > to fn:concat() cannot be expressed as an array-variadic call. I suspect > that this is the only standard function with this problem. > Therefore the raised question remains valid and needs an answer. > If an fn:concat function is array-variadic (provided array-variadic functions are supported), it can be implemented in terms of the `for member ...` expression, or another way of enumerating over the parameters. It is more complicated to get the same sequence-style flattening logic using arrays, but not impossible. That is, you could use something like: declare (: %variadic("array") :) function fn:concat($args as array(xs:anyAtomicType?)) as xs:string { string-join(for member $arg in $args return string-join($arg)) }; and that would have the same semantics as a version that is sequence-variadic, which could be implemented as: declare (: %variadic("sequence") :) function fn:concat($args as xs:anyAtomicType*) as xs:string { string-join($args) }; > >> >>> *Quote*: >>> "%variadic("map") indicates that the function is map-variadic. A >>> map-variadic function declares one or more parameters, of which the >>> last must be a type that accepts a map . >>> >>> *Questions*: >>> 1. Why not “record-variadic”? >>> Unlike the map type, the new record type allows for static typing (and >>> if dynamic typing is really necessary, an “extended” record type may be >>> used). Thus, using “record-variadic” will be an improvement over >>> “map-variadic”. >>> >> >> Note that record types are not specifically a new type, but are a subtype >> of maps (in that they provide additional constraints on maps). The current >> draft text permits either map or record types, depending on whether or not >> you want to restrict the values. >> > > Following this logic one can say that both: the record type and the map > type are examples of the function type. Then why not call all these > "function-variadic"? :) > We have the chance to use a most precise name, and this is > "record-variadic", unless someone can show an example of a standard > function that can be called as "map-variadic" (passing a map) but not as a > record-variadic (passing a record). > > So, this question still needs a definite answer. > Map and array types are function types in that they can be called like other functions can, so `$array(2)` and `$map("test")` are valid (in other words, you can cast a map/array to its super type, but not cast a function to its sub-type unless it is an instance of that sub-type). Maps and arrays have additional behaviour/logic that makes them incompatible with each other, and functions do not hold a collection of values, or a collection of (key, value) pairs. A record type defines the keys that are valid for a given map and the types those keys can have. As such, a record typed variable can be passed to a map (provided the key and value types are compatible), as a record type is a valid map. -- Therefore, a record-variadic function is a special case of map-variadic functions. The only difference between these is the validation/type checking and that is not dependent on the variadic nature of the function. That is, map-variadic rules will convert the argument names into a map (for a map or record type) and the RecordTest rules will check the validity of that resulting map. An implementation (or editor/IDE) could provide more helpful messages by placing the error on the argument name. > >> >> >>> 2. Is there any example of an existing standard function that cannot be >>> expressed/called as a record-variadic function, but can be called as a >>> map-variadic function? >>> >> >> I think it would be useful to define the map-variadic standard functions >> using RecordTests, as that would permit static-time error checking. >> > > Exactly, and this is why we should give it the precise name: > "record-variadic"! > But map/record-variadic functions are the same in how the argument names are built into a map (just like we don't have a record {...} constructor for record types). > >> I don't think we should be restricting this to how these are used in >> standard functions (otherwise you end up with a situation like with >> annotations where users can add their own annotations, but the language >> doesn't provide mechanisms to take advantage of them without vendor >> extensions/functions). For example, someone could create a json-object >> function that is map-variadic as it can take any parameters. -- Given >> Michael Kay's comments about someone not being able to find an array >> function, it may be useful to add an fn:array and fn:map function, in which >> case that would be an example of a map-variadic function that cannot use a >> RecordTest. >> > > Sorry, you lost me here :( > If we only define/allow record-variadic functions because no map-variadic functions are defined for standard functions, that would prevent users from defining/using map-variadic functions if needed -- that is what I meant by "restricting this to how these are used in standard functions". The json-object (or json:object) example (and the possible standard fn:map function) would have the signature `fn:map($map as map(*)) as map(*)` but cannot be defined as record-variadic in a meaningful way, as they can allow any key/value pairs. Additionally, a record test requires at least one key/value to be defined and it is not possible to specify that for a map that can take any values. Another example would be a map-to-xml function. > > >> >>> *General question*: >>> In the case when a function call can be array-variadic or >>> record-variadic, the caller may prefer to pass just one array (or a record) >>> containing the variadic arguments. In this case wouldn't it be good to >>> allow the ability to specify as a keyword argument this array (or record) >>> and have a standard name for these (for example "varargs")? I believe this >>> will improve the readability of the code. >>> >> My reading of the %variadic annotation is that it is an infered >> annotation that depends on how the function is declared, and is a way of >> adding a new property to functions without modifying the data model. >> Otherwise, the statement "%variadic, which is present on all static >> functions" would not make sense. -- It may be worth noting how the default >> value is calculated, like is done with %public/%private. (This also implies >> that you could turn off variadic behaviour by adding %variadic("no") to a >> function that would otherwise be variadic.) >> >> The intention is to have this be automatic, so a varargs or similar >> keyword should not be needed. For sequence-variadic functions, a possible >> syntax of `ItemType...`/`SequenceType...` has been discussed in other >> threads and the variadic arguments proposal in the xpath-ng project. >> > > I never said that naming the last argument was "needed". Just that using a > standard name for it will increase the readability of the code, when the > function call provides the complete array or record as the single last > argument. > Ah, ok, I see what you mean now. Thanks for the clarification. The term "variadic" is common -- "varargs" is a compound of "variadic arguments". In XPath/XQuery terminology, "variadic" would be the better version as you are creating a variadic function, and XPath/XQuery limit talking about arguments to where the function is called. Kind regards, Reece
Received on Sunday, 13 December 2020 22:14:48 UTC