-
Notifications
You must be signed in to change notification settings - Fork 18
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
The =?>
lookup arrow expression operator is weird, difficult to use, difficult to understand, difficult to read and unnatural
#1800
Comments
if |
We don't alter at all the current behavior of the |
=?>
lookup arrow expression operator is weird, difficult to use, difficult to understand, difficult to read and unnatural**
=?>
lookup arrow expression operator is weird, difficult to use, difficult to understand, difficult to read and unnatural**=?>
lookup arrow expression operator is weird, difficult to use, difficult to understand, difficult to read and unnatural
While I am generally not too fond of dropping features that we have already accepted in the past, I wouldn't mind getting rid of the particular Many of the questions about method references had already been discussed in #916, so it might be valuable to (re)consider them in the envisaged PR. For example, what would be the result of the following expression? let $a := { 'a': 1, 'f': fn() { ^a } }
let $b := { 'a': 2, 'f': $a?f }
return $b?f() |
This should have already been decided when publishing the PR for #916, thus, the same as the current equivalent: let $a := { 'a': 1, 'f': fn($this) { $this?a } },
$b := { 'a': 2, 'f': $a?f }
return $b =?> f() Please, do note that this issue is only making the already agreed syntax of #916 more user-friendly, and any questions about the more intrinsic rationale of #916 do not belong here. I remember that we decided that
Unfortunately, the BaseX fiddle is once again inoperative so this cannot be verified: Update: Now the fiddle is working and the result is as expected: 2 |
We didn't decide any such thing. It was an idea in the #916 proposal, but not the final decision. The name of the
I am sorry they definitely belong here, and they need to be clarified in an upcoming PR. While the semantics of
Thanks, we will look into that. It is just a testbed; feel free to get the desktop version if you want to have a safe environment. |
I concur that this operator has difficulties, and I don't think it would be an enormous loss to drop it. I would prefer to improve it if we can, but it might be that the only way to make it really usable is to move closer to full object orientation with classes, inheritance, and encapsulation. It would certainly nice to be able to make |
Yes, and let us spend another 3 weeks doing the most important work of our lives: designing the "ordered map" ...
We are very close to having a good formal description, without having to reinvent the OOP wheel.
Correct.
It is both possible, and not too-difficult, depending on one's definition of "difficult" and "easy". Are there any problems that you see in the following formal definition? Here is the formal definition: Map Execution Context - MEC or MC:The map execution context is the stack of maps on which function calls are being evaluated, and each of which is initiated with the following type of expression As part of the evaluation of any such function call, depending on the body of the function, another, contained function call (of this same type) against a map can also be under evaluation, and within the evaluation of the body of this nested function call yet another function call (of this same type) against a map can also be under evaluation, and so on..., and so on. Therefore, within the evaluation of an expression there is a stack (maybe empty) of such maps, function calls (of this same type) upon which are being evaluated. Definition: The Map Execution Context (MEC) is a part of the dynamic evaluation context. Its value is the stack of maps upon which function calls are being evaluated, each of these using the syntax fn:get-map-execution-context()Summary Signature
Properties Map Sibling Lookup operator
|
I think this definition is problematic. It implies a loss of referential transparency: the expression I think it's possible to avoid this problem by relying on labels attached to function items. We can say (indeed, in particular circumstances it is already the case) that evaluating
A complication is that we need to define very clearly under what circumstances a function item loses its label. For example, what happens when the function item is added to another map, or when the function item is partially applied. The existing spec for labelled items ought to cover this, but it needs careful checking. |
I am trying hard to understand this. What is the problem? The definition of Map Execution Context is precise and the rule is precise: for any function whose body contains a Also, I don't understand at all how this could be meaningful:
But still, for whatever function-member is selected for execution, the above rule still applies. What is the problem? Cannot discuss "solutions to the problem", if it is not clear what the problem is. And, btw, you quoted the rule incorrectly. The expression in it is not:
But the expression in the rule is:
MHK> Sorry, that was a markdown tagging error. |
The problem is that the way you have tried to define it, |
Definitely not a problem if And when |
You say
I don't understand that answer. You say that a map is only added to the stack by an expression whose syntactic form is You also say:
I'm sorry, but it isn't. You define it as:
There are lots of unanswered questions here. What exactly is And this still leaves the problem of referential transparency: the dynamic function call $F($ARGS) now has different results depending on the syntactic form of the expression that computes $F, which causes havoc for optimisers trying to rewrite expressions with their equivalents. You wouldn't expect, for example, that |
What if we dedicate a special operator for "method execution", but a simpler one than the current Could we then instead of having to write: let $rectangle := {
"width": 20,
"height": 12,
"area": fn($this) { $this?width * $this?height }
} use the below as a lexical synonym: let $rectangle := {
"width": 20,
"height": 12,
"area": fn() { ^width * ^height }
} And cause the evaluation with: This is still better and more convenient than the current specification's: |
I propose the alternative solution:
What this means is that the person declaring a record type can write:
and the person invoking the method can write The benefits of this proposal are:
The main downside is that it involves a tweak to a low-level primitive operation, namely selecting an item from a map. But I think we can live with that. (An alternative would be to do this only for a subtype of maps which we could call objects. But introducing such a subtype would involve much more extensive changes, opening the door to additional requirements such as inheritance, object identity, and encapsulation.) |
This is indeed a significant step forward. As a next step, let us drop the Then, finally, let us have a rule that Then the above example will be written as: let $rectangle := {
"width": 20,
"height": 12,
"area": fn() { ^width * ^height }
} and the caller invokes the function: |
A function is not a member of a map at the point it is created; it is created first, and then added to the map later. That's why identifying the function as one that qualifies for this treatment has to be part of the expression that constructs the function, not part of the expression that adds it to a map. |
Sorry, I totally don't understand this. let $rectangle := {
"width": 20,
"height": 12,
"area": fn() { ^width * ^height }
} The above function A function can never be "added to the map" - because maps (as everything in XPath) are immutable. "Adding a function, or any new member, to a map" creates a new map, and the function is associated with the new map at the point of this new map's creation. Isn't this true? |
It is ironic to see that this discussion is basically a sequel of what was initially proposed in #916 (👍).
Note that the function is created before it possibly becomes part of a map. It could also be declared outside the map: let $f := fn() { map:size($this) }
return { 'size': $f } But it’s true, maybe the static context of a function body could implicitly be augmented with I would still have some sympathy for an |
💯
Yes, I prefer this to be a function, and actually don't care too much, provided that this function is only called behind the scenes and the user doesn't have to reference it explicitly. |
I think there are many cases in which users will want to do more than just referencing map entries. Next, it will certainly not be obvious to everyone what If we come to the conclusion that we need new syntax, |
This aside, I am glad we kinda agree on the important things here.
Well, I have the advantage to be just a user, not an implementor. So, yes, users need the simplest possible forms of expressing the reference to sibling-members. |
Subexpressions are conceptually evaluated first, and the resulting values are then used in the evaluation of the containing expression. Unless the containing expression defines a custom static or dynamic context for evaluation of its subexpressions, the evaluation of the subexpression takes no account of where it appears. It would be possible to say that a map initializer sets a special static context for its operands that makes any function automatically behave as a method, but I don't think that would be a good idea, mainly because I think it's better if the way you construct the map and the way you construct the function item are completely orthogonal to each other - orthogonality in language design is a good thing to aim for. |
We have all the machinery for adding variables locally to the static context and for binding values of those variables to the captured context of a function item. We don't have corresponding machinery available if it were a function. It could be done, but would involve a lot more complex underpinning. Plus, |
We already saw, in a previous comment, that:
The function is not changed in any way, but the exact context in which it is executed is different in these different cases. As for orthogonality, the pair |
My personal opinion is that each of: |
@ChristianGruen wrote:
A function item "becoming part of a map" is surely something that happens dynamically, so how can it change the static context? @dnovatchev wrote:
I'm not at all sure what was intended by that statement. Note that in my proposal the function/method is not changed in any way at the time it is added to the map. The captured context of the function (specifically, the binding of
returns 36. |
Exactly! The functions: Thus the pairs The results of the two function calls to the same function and with the same (empty) arguments-tuple values - these results are different, because the contexts (and specifically the value of the ( Alternatively, one could argue that Anyway, we all agree that a map value that is a function has a context that includes the special variable Can we move forward to presenting this in the documentation? |
Thanks, nonsense the way I wrote it (vacation standby mode). I had catch clause variables in mind, which are bound later on. A hardcoded |
👍 🥇 ❗ 💯 🍦 |
I'm currently thinking about the problem: what happens when a method (a function item annotated as This happens implicitly when you do I think the cleanest solution is probably to say that the binding of This does mean that It still leaves the question of what happens when a method is bound to a particular map and is then added to another map: for example
We could define this one either way, and I don't think there's an obviously right answer. We could also perhaps make it an error (the expression |
Wow, so much to digest just after some days of absence.
My experience is that many people prefer to write From the implementer perspective, if possible, we use one implementation for all variants by rewriting 3.1 lookup operator constructs:
|
The XPath 4.0 language now includes a way for a function defined as a member of a map to easily access other members (siblings) that belong to the same map instance. Special syntax, the
=?>
operator, was introduced to call such a function. As a whole this is a huge step forward providing the user with a new, powerful mechanism to conveniently express relationships and calculations over several member-values of a map instance.I am raising this issue with the goal of further improving and simplifying for the user the way to define and call a member function of a map/record, giving it a convenient way to access the values of other members of the instance of the map, on which the call has been issued.
In my work, I have been trying to define a number of functions that must belong to a map/record and that should be able to access other members of the same map/record to which these functions belong.
The experience was far from satisfying and here I describe the main problems I encountered when trying to use the
=?>
operator, and some obvious suggestions how we can further simplify the syntax for calling any member function of a map or record.1. Problems trying to use the
=?>
operatorHere are the main problems I ran into.
Problem1. The
=?>
operator was:It would be much better if we didn't have to use any special operator at all in order to call a member function "
myFunction
" of a map$m
by simply:$m?myFunction(<tuple of any arguments defined in the signature of the function>)
Problem2. There is no example, in the sections that describe the record type (3.2.8.3), showing a record member-function that accesses the values of other members of the same instance of the record.
Thus, the new feature is effectively hidden for people who want to work with records.
We need such an example for a record, so that we don't forget that any record is also a map and possesses all functionality a map has to offer. And a statement to this effect must be added to the description of records.
Problem 3. This syntax is overcomplicated and difficult to use and remember, resulting in unnecessarily long and complex expressions:
It would be significantly better to use a much simplified syntax such as:
Recognizing that
?name
is already used since XPath 3.1 as Unary Lookup Operator, and to avoid the unlikely case of collision, when a member function accesses other members of the map-owner-instance that happen to have identically the same names as expected constituents of the current context item (upon which the function is applied), we can introduce a special character to denote the current map-owner-instance, thus the above example could look like this:Solutions
Solution for Problem 1 above (weirdness of the
=?>
operator:Do not introduce any special operator. Just use
?
to invoke the member-function.Solution for Problem 2 above (lack of example of a record having a member-function that accesses other members of the same map-owner-instance).
Obviously, provide such an example. Also reiterate there that all features and functionality of a map continue to be available for records.
Solution for Problem 3 above (overcomplicated syntax:
=?>
operator. Use?
for all references to member-functions.$this
. For example, the current example in the documentation:"area": fn($this) { $this?width * $this?height }
should instead be:
"area": fn() { ^width * ^height }
^
character to denote owner-map-instance membership. Thus^width
means: "The member named "width" of the map instance upon which the current function was invoked"Conclusion
I will issue a PR with the solutions, provided there are not any substantial comments hilighting problems with this proposal.
The text was updated successfully, but these errors were encountered: