18872, "bradcray", "Support "auto-read" on objects?", "2021-12-15T19:11:16Z"
opened 07:11PM - 15 Dec 21 UTC
type: Design
area: Language
This issue is asking whether there should be a way for an object to say "When us… ed 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 https://github.com/chapel-lang/chapel/issues/17999#issuecomment-974488681 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:
* create a Chapel object that acts as a stand-in for the unavailable memory location
* return such an object through dsiAccess() instead of the element itself
* do all such returns through non-`ref` versions of `dsiAccess()` since a newly-created local class/record can't be returned by reference
This 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-argument `this()` method to minimize the syntactic overhead that needs to be added. So for example, they can write things like:
```chapel
myArrayOnDisk[i] = ...; // no problem, calls `=` overload with object as LHS expression
... = myArrayOnDisk[i]; // problematic since it returns an object rather than an `eltType`
... = myArrayOnDisk[i](); // the workaround: call a 0-argument `this()` method
```
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:
```chapel
record R {
var wrappedVal: t;
}
```
then I could write:
```chapel
proc R.this { return wrappedVal; }
```
which would cause `... = myR;` to essentially become `... = myR.wrappedVal`. From an interface perspective this seems attractive in its orthogonality to paren-ful `this`, 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 (https://github.com/chapel-lang/chapel/issues/16238) as well as potentially supporting direct reads/writes (https://github.com/chapel-lang/chapel/issues/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-less `this` 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 (https://github.com/chapel-lang/chapel/issues/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.
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