Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Representing key-value pairs #357

Closed
michaelhkay opened this issue Feb 18, 2023 · 4 comments · Fixed by #446
Closed

Representing key-value pairs #357

michaelhkay opened this issue Feb 18, 2023 · 4 comments · Fixed by #446
Labels
Feature A change that introduces a new feature XDM An issue related to the XPath Data Model

Comments

@michaelhkay
Copy link
Contributor

michaelhkay commented Feb 18, 2023

A map can be decomposed into, or composed from, a sequence of key-value pairs (KVPs).

There are two natural representations of a key-value pair (K, V): it can be represented as a singleton map (map{ K: V }) or as a "doubleton" map (map{ 'key': K, 'value': V}).

This issue examines how well either of these representations is currently supported, which of them is preferable, and how this support should be improved.

I'll consider the following basic operations: constructing a KVP from a key and a value, assembling a map from a set of KVPs, decomposing a map into a sequence of KVPs, extracting the key from a KVP, and extracting the value from a KVP.

Singleton Representation

Constructing a KVP from a key and a value:

map{ $key : $value }
map:entry($key, $value)
<xsl:map:entry key="$key" select="$value"/>

Assembling a map from a set of KVPs

map:merge($kvps)
<xsl:map>

Decomposing a map into a sequence of KVPs:

map:for-each($map, map:entry#2)

Extracting the key from a KVP:

map:keys($kvp)

Extracting the value from a KVP:

$kvp?*

Doubleton Representation

Constructing a KVP from a key and a value:

map{ 'key': $key, 'value': $value }

Assembling a map from a set of KVPs

map:build($kvps, ->{?key}, ->{?value})

Decomposing a map into a sequence of KVPs:

map:for-each($map, ->($K, $V){map{ 'key': $key, 'value': $value })

Extracting the key from a KVP:

$kvp?key

Extracting the value from a KVP:

$kvp?value

Analysis

The singleton representation is better supported at present, and it makes sense therefore to fill in the gaps that currently make it awkward. The main attraction of the doubleton representation is the ease of extracting the key and the value using $kvp?key and $kvp?value. The equivalents for the singleton representation (map:keys($kvp) and $kvp?*) feel clumsy and unintuitive; however, it's not at all obvious what would be better, short of introducing new custom syntax, which seems over-the-top. The best idea I can come up with is to have two functions map:key($kvp) and map:value($kvp) which require $kvp to be a singleton map. But I hate the namespace prefixes...

The other thing needed to "fill the gaps" is a function map:entries($map) equivalent to map:for-each($map, map:entry#2).

What if we chose to go the other way, and improve support for the doubleton representation?

We could add map:key-value-pair($key, $value) to create KVP, and map:of($kvps) to build a map from a set of KVPs, and map:key-value-pairs($map) to decompose a map. The trickiest problem is what to do about XSLT, where the 3.0 instructions <xsl:map> and <xsl:map-entry> use the singleton representation.

@dnovatchev
Copy link
Contributor

dnovatchev commented Feb 18, 2023

Well presented!

I don't feel any need for the "doubleton map". BTW, isn't it more precise to call this by its true name: a set of record ?

We don't achieve really anything more efficient in writing $m?key and $m?value rather than map:keys($m) and $m?*, especially when we could rewrite the latter to: $m => map:keys()

Maybe we can introduce "map-record" as a synonym for "map entry", but better defined as a record and not as a map.

Then a map is exactly a set of map-records, we can have a function map-records($m as map()) as record(item(), item()*)* which returns the set of all map-records of $m

.

@dnovatchev
Copy link
Contributor

dnovatchev commented Feb 19, 2023

I vaguely remember that there was some proposal that would allow us to skip the namespace of a particular standard function and let this be automagically deduced, for example, in the case of $m being a map, then in these expressions: $m => keys() or keys($m) the processor could deduct that the reference function keys() should most likely be map:keys() as its only argument is a map.

Or, this can be accomplished by an intelligent XPath IDE and it will add the namespace for us automatically, while we are typing.

@rhdunn, what would an IDE developer tell us about this? Is this possible? Realistic?

As whether this will it be valuable for the end user, I truly believe so, and that this value must be obvious.

@rhdunn
Copy link
Contributor

rhdunn commented Feb 19, 2023

An IDE (or language implementation for the IDE) could filter the functions it finds based on the context it is in at the point of the auto-complete behaviour.

In the case of an ArrowExpr with a known type for the left-hand side (a map in your case), it could filter the functions to only display the functions -- such as map:keys -- that take a map/record type as the first parameter. That list would include the functions in the map namespace, but would also include other functions such as those in other namespaces. The auto-complete would fill in the correct namespace prefix when the user selects a function in the auto-complete list.

From the language perspective, there would need to be a mechanism to add multiple function namespaces to the default namespace, or to import functions into it.

The challenge with functionality like that is where a function exists with the same name and arity. In those cases, it is not possible to determine which function to use (in both function calls and function references). XPath/XSLT and XQuery do not support type/signature-based function overloading like other languages, so there would need to be another mechanism.

For example, you could have a mechanism where you have to provide implementations of those conflicting functions to specify how to resolve it. That could be by delegating to one of the namespaced functions. It could also be defining your own dispatch-based implementation using a typeswitch statement. Or it could be something to signal not to include that function in the default namespace.

@dnovatchev
Copy link
Contributor

dnovatchev commented Feb 19, 2023

Thanks Reece!

For me as a developer, a good thing the IDE could do is to display as a prompt a dropdown-list with function-candidates and ideally the function that we mean is at (or near) the top of that list. Of course, if the list has just a single function-name and namespace-prefix, these could be inserted directly into the edited text.

@ChristianGruen ChristianGruen added XDM An issue related to the XPath Data Model Feature A change that introduces a new feature labels Feb 20, 2023
@ndw ndw closed this as completed in #446 Apr 18, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Feature A change that introduces a new feature XDM An issue related to the XPath Data Model
Projects
None yet
4 participants