Re: New XPath draft

On Sun, Dec 13, 2020 at 2:14 PM Reece Dunn <msclrhd@googlemail.com> wrote:

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

What issue? If the last positional parameter/argument is of type array,
then the last two of the list of arguments in the "effective call" will
both be arrays and there is no ambiguity in their interpretation: the last
but one argument (array) is the last positional argument, and the last
argument (array) contains the variadic arguments.



>
> 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)
>     };
>
>
Great, then this means there is no reason why map-variadic function calls
should exist. All of these can be record-variadic, thus more amenable to
static type-checking.


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

Yes, so we both agree that in a record-variadic function call a stronger,
static type-checking is possible, than a map-variadic method call (which is
not a record-variadic method call).
Good result!



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

But the major advantage of record-variadic functions is the stricter
type-checking of the variadic arguments provided in the function call.



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

I asked for an example of even one function that can be declared as
map-variadic but cannot be declared as record variadic. If we have such an
example, then this would justify using map-variadic functions. If not
(which is the case at present), then there is no justification to declare a
function as map-variadic and not as a record-variadic.



>
> 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.
>
>
Great!

I would agree with whatever name is considered best by our native-speakers
of English :)


> Kind regards,
> Reece
>


Thanks again, Reece :)

Received on Sunday, 13 December 2020 23:12:14 UTC