-
Notifications
You must be signed in to change notification settings - Fork 15
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
From Records to Objects #720
Comments
On first reading seems quite good.
In fact there are 4 of these 😄 Some observations:
|
Thanks for your comments.
Indeed, I am deliberately trying to make changes that are relatively modest in scope rather than redesigning the type system from the top down. I think small steps are more likely to be successful.
This is why I linked this functionality to the use of a constructor function for a map, which is a new capability. A regular function added to a map using
Tunnel parameters have dynamic scope. The $this variable is intended to be present in the static context of the relevant function body, and to be part of the non-local variable bindings of the function item within the map.
There's no such function. Rather there's a perfectly normal variable $this added to the non-local variable bindings of certain functions.
No, because this proposal doesn't change the data model so that maps have parents and ancestors. Rather it provides a way of creating a map M in which one or more entries are function items that have a non-local variable binding $this whose value is M.
No, that's not the mechanism. I'm not proposing to change the fact that a map M can be added as an entry in many other maps, none of which is a unique or distinctive container or parent of M.
In separate proposals I have indeed been advocating this capability. Since we already have the mechanism of annotations on function items, I have been proposing that we should build on the existing concept of annotations rather than inventing a new but very similar concept of system properties. |
Exactly due to this we should not use the term "object" which in all other languages means the root of the type hierarchy. If this is just for maps (records) we need a more modest name.
It seems illogical why map-values (that are functions) can only in some cases have the $this variable, but cannot in other cases.
if a map (record) is created via a constructor, then it has a limited scope and when it goes out of scope, so does its
Having a new standard function (
Agreed.
No, we don't - only XQuery has annotations.
I find the concept of functions that are members of "objects" but have some properties that are defined outside of the object rather disturbing and confusing. Maybe this is result of using the term "object" - and this would be equally confusing and disturbing to any other software developer, who already knows what an "object" is supposed to mean. Anyway, a function defined within an object should have all its properties as part of this defining object. Having some of its properties here and some of its properties there means that this function does not completely belong to (is not completely defined by) its object. So much about an object having a function - all this concept is destroyed/blown-away by the fact that this function has some properties that are not part of its object. To summarize: We have 3 major differences of understanding this proposal:
|
On reflection, the proposal for implicitly binding a variable This starts to feel a bit similar to the proposals in issue #596, where we propose that if a map that is marked as being "pinned", a lookup operation on the map will pass the identity of the map as a special property on the value that results from the lookup: in this case the |
Yes, |
A revised proposal (in its entirety):
The effect of this is that you can now do:
and We should add function annotations (or at least the %method annotation) to the XPath grammar. A possible variation would be to add the magic treatment of %method functions to map:get($M, $key) and to $M($key) as well as to the function lookup operator. On the whole, though, I'm in favour of doing it only for the lookup operator. |
The ambition to introduce object-like structures in the language is definitely appealing; out of the box, I get many use cases in mind which could be realized much easier this way. I have some general concerns that we overstrain our existing language features to “just make it work”. I understand the proposed rules, but I am afraid that some users could be overwhelmed by the interplay of the concepts that have originally been introduced for something else. Looking at… declare function r:rectangle($x, $y) as record(x, y, area) {
map{"x":$x, "y":$y, "area": %method fn() {$this?x * $this?y}}
} …people might, among other things, need to understand that:
In the following, I’m proposing a syntax that feels more consistent and intuitive to me. I’m bold enough to ignore the implementation details. I’ve also skipped the idea of generating a constructor function for records (which I actually find attractive): declare function rectangle($x as xs:integer, $y as xs:integer) as record(x, y, area) {
record { x := $x, y := $y, area := fn { $this?x * $this?y } }
};
rectangle(4, 8)?area() The ideas here:
To push this further: declare type rectangle as record(
x as xs:integer,
y as xs:integer,
area := $this?x * $this?y,
*
);
declare function rectangle($x as xs:integer, $y as xs:integer) as rectangle {
record rectangle { $x, $y, is-square := $this?x = $this?y }
};
|
I like your proposal as it is simple and clear. Do I understand correctly that with this proposal we don't need any I feel uneasy about introducing the annotations syntax in XPath. Annotations are just masked decorators, and when we have the power of decorators anyone can create any "annotations" that come to mind on the spur of the moment. |
I would hope so. Michael has been much more explicit about implementational aspects, but I think it could be possible to prepend I'm not really thrilled about introducing new magic variables (imho it’s ugly to have them in the
I'm not sure. How would you specify the existing |
@ChristianGruen I was trying to take things one step at a time: get the primitives in place first, then build syntactic sugar on top of them. I agree that record constructors would be useful (but should they be custom syntax, or named functions?), and that it might well be useful for functions within record constructors to behave this way by default. But the key thing I was trying to establish is: what exactly is the mechanism that causes I don't think that's an "implementational aspect"; it's fundamental to the semantics, even though we might hope that we can devise a sufficiently intuitive syntax that most users won't need to think about it. |
Yes, using a function instead of a variable eliminates the possibility for any naming conflicts with pre-existing code having a variable named It would be best if we do not introduce a list of banned-names in the language |
Makes absolute sense…
…but I think it's not only syntactic sugar. I am not sure if we should introduce method semantics or
Maybe yes… How would that look like?
I see. Could it work to add We could also bind the currently generated record to the context ( declare function rectangle($x, $y) {
record {
x := $x,
y := $y,
is-square := ?x * ?y
}
}; The resulting syntax would be similar to how record entries are accessed later on: rectangle(4, 8)?x |
The main advantage of using a variable is that we already have the concept of "non-local variable bindings" in function items, but of course we could expose the magic variable through function syntax if we prefer. Another benefit, however, is that at present the set of in-scope function names is constant throughout a module, whereas the set of variable names varies. |
With syntax like this: declare function rectangle($x, $y) { we need to be very clear what is the result of The specification I proposed states clearly that with a function annotated as a method, the binding of |
This will not be a problem if the specification mandates that Because the new record's constructor is called, inside the constructor A purist may want to have a separate : |
It would probably be illegal, as
Using the context value inside functions would be tricky indeed, unless the object is not passed on as argument… Which is already possible with maps: let $rectangle := map { 'x': 3, 'y': 4, 'area': fn { ?x * ?y } }
return $rectangle ! map:put(., 'y', 6) ! ?area(.) I can think of plenty of other questions. For example, what is going to happen if I may be too hesitant here, but will we manage that the result will feel better than a proof of concept? Wouldn’t it be better in the long term (possibly in a later version of the language) to design a new function subtype for objects with (pseudo-)mutable and immutable components, and with dedicated functions exist to “modify” (i.e., create a new object with updated) mutable values? |
@dnovatchev The concept "creates a new record of the same type" doesn't fit with the type system as currently defined. A record is (at present) simply a map, and it conforms to an infinite number of record types. Furthermore, adding a new entry to a map creates a map that doesn't necessarily conform to the same type as the original. |
@ChristianGruen "I may be too hesitant here". I'm inclined to think we can achieve something useful and powerful without fundamental changes to the data model or type system. I think we have most of the necessary building blocks in place, we just need to assemble them in the right way. |
If I want a "this" parameter in my function, why don't I just declare it and pass it explictly? |
because it's more convenient to do |
(caveat...I've only very recently started passing maps around with functions embedded in XSLT, its something I do a lot in other languages, including multi paradigm ones in preference to objects, but in XSLT I'm very much learning how to do this, so my views may be skewed by these other language contexts) wrt your example, fair enough, to be honest, for me its not a massive cost. Part of the pain in your example is viewing the record as an 'object' and then passing the dictionary to itself seems unnecessary. For me there are 2 dictionaries and the caller code would look like.
which doesnt look unreasonable, but I accept this is probably a minority view and highly subjective, and having records that support "this" doesnt preclude working in either way. To me XSLT always feels more functional than OO to me (all that matching and immutability) so for me it grates slightly and "dictionary passing" seems more natural. If the motivation is simply make caller code 'lighter' then maybe a new operator would be simpler $myrectangle?>area() that automagically passes the record into the function as the first parameter, then you don't need the baggage of objects, and have the convenience of OO like syntax...like extension methods in C#...I quite like extension methods...convenient syntax without all the object baggage. |
@michaelhkay I wanted to see how painful this was without 'objects'. 'fix' etc only need to be written once (I could not work out how to force saxon to be lazy, and I'm not convinced this state monad construct works in general, I feel I may be cheating), ignoring that, the cost was. So contextually (i.e. we do this once ever): but for the specific issue though: it obviously isnt as easy as writing "object".
|
@MarkNicholls As for laziness, please see this: #670 (The trouble with XPath‘s fn:fold-right. A fix and Proposal for fn:fold-lazy) and this: #716 (Generators in XPath) |
@dnovatchev - I looked at that (in some blog i think), it made my head hurt, I'm a novice of 2 days of anything but 'paths' in xpath - I needed something much more trivial, I just wanted to declare an expression as lazy, then i could implement fix, then i can fix my constructor introduce 'this'. The idea was just to at least measure the pain (for me at least), before considering the remedy. ... actually 716 does make sense.....I had hoped that xsl:iterate was just such a (deferred at least) construct, but it seemed eager so I rolled my own. I think i could probably roll it in this style. |
See #916 (comment) for some feedback on Michael’s proposal (https://qt4cg.org/pr/916/xquery-40/xquery-40-autodiff.html). |
I hope it is still not too-late. I propose to have a simple syntax for invoking a "member-function" with key myFunName on a map $m like this: $m ?> myFunName({any-arguments-here}) The rules are simple:
|
It has become idiomatic to use maps, and record type definitions, to declare a collection of functions; so for example the random-number-generator object offers a "method"
next()
that can be called using the syntax$rng?next()
.The problem is that it's not possible, within the XPath/XQuery language, to implement such a function with implicit access to the object on which it is invoked. The implementation of the function does not have access to any kind of
$this
variable.This issue considers how we can move forwards from supporting simple records to introduce object capabilities, in an incremental and compatible way.
Here are three steps in that direction:
declare item type my:loc as record(longitude as xs:double, latitude as xs:double)
you also get a constructor function allowing my:loc(180, 180), allowing both positional or keyword arguments corresponding to the field names,
Allow default values to be defined in the record type, which act as default values for the parameters in the constructor function.
Allow functions that are defined as part of a record type access to a variable $this. The constructor function provides an implicit binding of this variable to the record/map/object that is being instantiated.
Allow self-reference to a named record type (and its constructor function) within the record definition.
So you can now do:
and then
which returns 1.
The text was updated successfully, but these errors were encountered: