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
Support "auto-read" on objects? #18872
Comments
Tagging @mppf and @aconsroe-hpe on this because of their involvement on #17999, @mppf because of his prior work on user-defined coercions, and @ronawho on it because of his interest (I believe) in direct reads-from/writes-to atomics. |
In my opinion here, the main challenge here is, as you said, deciding when to do the "read". If we have a type (call it // suppose myWrapper has type wrapper
var a = myWrapper; // should y have type `wrapper` or `int`?
var b: int = myWrapper; // "implicit conversion" to int?
proc f(arg: int) { }
f(myWrapper); // "implicit conversion" to int?
... are there other interesting cases here? Other than the
That is my view.
IMO even those reluctant to add them acknowledge that some patterns are impossible to implement without them.
I'm curious if you have any reaction to the later comments in #17999 -- starting from #17999 (comment) . But that is probably for that issue. |
I'm intrigued and while I do find this related to implicit conversions because you are changing one type to another, I think I find it more related to the ideas of a lazy value or a future especially because we've pointed out the temporal and side-effecting aspect of this all. I like the paren-less I can imagine two different implementations for the arrayOnDisk example. I think they would both be possible with the paren-less /* Impl 1 */
record ArrayOnDiskView {
var filename;
var bounds;
var arr;
var fetched = false;
proc this {
if fetched {
return arr;
} else {
arr = readFromDisk(filename, bounds);
fetched = true;
return arr;
}
}
}
/* Impl 2 */
record ArrayOnDiskView {
var filename;
var bounds;
proc this {
return readFromDisk(filename, bounds);
}
} I definitely see the motivation in a generic programming context. Would there be a way to explicitly call the paren-less proc foo(x:ArrayOnDiskView) ...
proc foo(x:[])...
foo(myArrayOnDiskView) // ambiguous? can I force one or the other? |
proc foo(x:ArrayOnDiskView) ...
proc foo(x:[])...
foo(myArrayOnDiskView) // ambiguous? can I force one or the other? If we view it as an implicit conversion, the |
I think you could say That said, though it's cute, I think my main hesitation about taking the paren-less
That's an interesting point that makes me worry that it would be too limiting (so potentially another reason to avoid paren-less this as an approach). E.g., |
The last effort I made towards language design of implicit conversions was #16729. Some of these questions we have already discussed there (and to some extent in #5054 -- #5054 (comment) being my favorite comment on that issue :) ). Anyway I am still happy with the proposal in #16729 -- namely |
This issue is asking whether there should be a way for an object to say "When used in a value/read context, should there be a way for me to give a value other than myself?"
Background / Motivation
I'm spinning this issue off of #17999 (comment) where I reported on a team's ability to express some nontrivial data access patterns in Chapel by having an object that served as an intermediary for the real data value. As background and motivation, imagine creating an array implementation in Chapel that is stored on disk, or in some other medium where implementing the array's accessor function (
proc dsiAccess(indices: ...) ref
) is complicated by the inability to return a Chapel reference since the element isn't "in memory".The approach being taken by this team is to:
ref
versions ofdsiAccess()
since a newly-created local class/record can't be returned by referenceThis is generally working in the current prototype, but has the downside that if the object returned by the dsiAccess() is in a RHS / read context, a method needs to explicitly be called on it in order to read the value. In the write context (the focus of #17999), the
=
operator can be overloaded for the object to make those work transparently. In the read context, the current prototype uses a 0-argumentthis()
method to minimize the syntactic overhead that needs to be added. So for example, they can write things like:This makes the prototype work but is obviously problematic in that (a) it makes uses of this array type different than any other which means that (b) existing generic array code can't be applied to these arrays.
This leads to the question in the title and top of the OP: Should there be a way for an object to say "When I'm referenced in a value/read context, call this method / take this action rather than referring to me directly?"
Possible Solutions and Other Contexts
Paren-less this method?
My first "so crazy it just might work" idea here was to permit objects to support paren-less variants of the
this
method that would indicate exactly this. E.g., if my record were:then I could write:
which would cause
... = myR;
to essentially become... = myR.wrappedVal
. From an interface perspective this seems attractive in its orthogonality to paren-fulthis
, though the challenge is when the compiler would apply it.Utility for Atomics
My next thought was to realize that, when using atomics recently, I've been thinking about our long-term hope to have simpler ways of applying operations to atomics (#16238) as well as potentially supporting direct reads/writes (#16237). Namely, I find myself wanting these all the time, and particularly recently. This made me wonder how hard they would be to implement, where I mostly think "quite easy" except for the read case, which feels like another instance of this pattern: When I have an atomic in a read situation, I simply want it to evaluate to its value, through its
.read()
method. So maybe a solution to this issue would help move that forward as well.Wait, what about Syncs?
Next, I realized that, up until very recently, we've supported direct reads of syncs, which are similarly implemented under the hood using objects, which made me wonder whether we could leverage that approach here as well. That said, while it worked, that approach always felt a bit clunky and heavyweight (and wasn't designed to be user-facing). Specifically, IIRC, we wrapped every "read" expression with a routine that became a no-op for all types other than syncs(?) (right? Typing that, it sounds so ridiculously heavyweight that I find myself doubting that I'm remembering correctly...).
User-defined coercions?
So next, I started thinking about cleaner ways of addressing this issue without relying on the sync trick and realized that the big challenges in the paren-less
this
case seems to be "When does one apply this?" E.g., "try to resolve a function, and if you fail, see whether any of the arguments have paren-lessthis
methods, and if they do, see whether that makes things resolve better"? And once I thought of it that way, it made me realize that this is effectively exactly what the compiler's coercion logic does, and that perhaps user-defined coercions are the way of expressing this pattern (#5054).I know there's been reluctance around opening the door to user-defined coercions in the past because of its potential for abuse, but patterns like this seem to necessitate it—or something so similar to it that it's indistinguishable—making me think we should reconsider that reluctance.
Other ideas?
That's where my current thinking is, but I'm curious about other ideas as well, obviously. This is quickly becoming the main barrier to proceeding with this work.
Other use cases?
int
as a Chapel record rather than a primitive type. For example, imagine:Such an approach would also require the ability to "read" the object in order to get at the underlying C value.
The text was updated successfully, but these errors were encountered: