-
Notifications
You must be signed in to change notification settings - Fork 419
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
Mutability when yielding by default / const ref / out / default intent #22950
Comments
Thinking a bit more about my last conversation with Michael about So then thinking more about whether it's important to have a yield intent that says "copy out but allow the index variable to be modified", I'm not sure it's a necessity. In part because I don't think it's a common pattern, and in part because an iterator author could get the same effect by creating a new temporary variable containing the value to be yielded, yielding that, and giving the iterator Alternatively, we could someday support a syntax like |
[This comment starts with the rationale and ends with a proposal.] I would like to differentiate [const] references from values. If a piece of code receives a reference, its const-ness is inherent in the reference. For example, if it is a reference to a const array, it is unsound to treat it as a non-const reference. If an iterator yields a const-ref to a non-const array, it is the iterator's decision to prevent the loop from modifying the array. Cf. if a piece of code receives a value, for example 56, there is generally nothing inherent in that value that tells whether it should be const or not. I can see the exception to the above being non-POD records and owned classes. However, before we discuss these, let us look from another angle. When we have a formal with the default intent, we disallow modifying it within the subroutine when the concrete intent is by value. Even when the value is 56 and there is nothing inherently const or non-const in the value, as far as this conversation goes. We disallow modifications not because of any qualities of values, but because we want to prevent certain classes of programmer errors. What happens today with our loop index variables for value-yielding iterators stems from the same desire to prevent some programming errors. It is not because the value-yielding iterators object modifying their yielded values within the loop. Going forward, we want to preserve const-ness of loop index variables for iterators that yield by the default intent and inherit the const-ness from iterators that yield by [const] ref. Yes, this implies we have a bug today. The interesting case is the W.r.t. the for constI in myIterator() {
var i = constI;
// the rest of the loop works as if it were
// for var i in myIterator() ...
...
} except for records with non-trivial or disabled copy initializers. In this latter case, a value-yielding iterator may indeed wish to disallow modification of the value it yields. We should come up with a good example when that happens, I expect that such example exists. Toward that end, I see two choices for the
I suggest that the common case is for the iterator to not care whether its yielded values are modified in the loop. This makes me favor the first option, "allow |
I absolutely think this is a bug.
I do not think
The IMO, the question of whether or not the result of Either way, I don't think we need Summing up:
|
@mppf: Just to make sure I'm understanding your vision w.r.t. this bullet:
I think you're saying that the default yield intent for an iterator is The reason I'm not confident that I'm interpreting that right, though is:
Specifically, if I write
Definitely. In my Presumably a corollary would be that for a procedure with an explicit I think the reason I keep coming back to the question of whether
If I were the only Chapel dev, I find myself thinking that I'd probably not support an But then, where I start to balk is "does that mean argument intents should also support (It's this line of thinking / attempt to compare
I agree that it's sufficient, and wouldn't change anything here now. But since we already support But this part of the discussion is definitely not crucial, nor on any critical path in my mind, and we can (and probably should) drop it from this discussion. |
@bradcray - Michael and I agree that the source of the thing passed by an |
Yes, I think there is some subtlety. I think we are agreeing that
Let's consider the function case for a minute, which is perhaps more straightforward. var global = 1;
proc returnsRef() ref { return global; }
proc returnsConstRef() const ref { return global; }
proc returnsValue() { return global; }
proc acceptsRef(ref arg) { }
acceptsRef(returnsRef()); // OK, we can pass the mutable reference
acceptsRef(returnsConstRef()); // const-ness error. Can't mutate a const ref.
acceptsRef(returnsValue()); // lvalue error - mutating the temp is probably not what you meant to do The point here is that Now, extending it to iterators, consider the case of an iterator yielding by value. If the iterator is yielding by value, I think of Coming back to your question more directly:
The
I don't think the iterator should be able to opt in or out. I think it should be the same for all functions/iterators returning by value and that's just how the language is. Now, let's go a bit further in exploring this lvalue error idea for loops. I've already said that I think this should be an lvalue error: for x in yieldsByValue() {
x = 42;
} But, what if we had this: for x in yieldsByValue() {
x = 42;
writeln(x);
} Now By making it an lvalue error, we are requiring a user in this position to write for x in yieldsByValue() {
var copy = x;
copy = 42;
writeln(copy);
} I think that's good because it makes it very clear that the assignment is not going to impact the data structure being iterated over. |
OK, thanks for the explanation, though now that I (think I) understand your response, I would've answered my question slightly differently:
This isn't the answer I was expecting given the rest of your response, which makes me think you didn't understand my question / I didn't ask it clearly enough / I was misinterpreting the statement that led to it. Specifically, I think you thought I was asking how I could create an iterator that would permit me to modify the loop index variable without modifying the original expression back in the iterator. In reality, I was trying to make sure I understood how I would create a range-like iterator vs. an array-like iterator. It looks to me like my guess from the previous response was correct, which I'll restate as: To create an iterator that can't modify its index variables, you would apply the explicit I think with that, we could consider this issue closed if we have the appropriate follow-up items accounted for:
|
I've made a note in my task https://github.com/Cray/chapel-private/issues/5235 to describe the |
I haven't talked directly about it to anyone about it other than you and Vass, but think the three of us agree? I think we verified that the current implementation—whatever it's doing—always disallows direct modifications to the index variable of an iterator that yields by default intent, so arguably we've already got this behavior, where we just might not be using the same When you talk about implementing it, are you talking about just implementing a heuristic that's maybe more in line with what we'll ultimately want? (e.g., maybe today we always use Do you have other anticipatory thoughts about things you think the implementation will or won't reveal or surprise us with? (I don't). |
Yes
Yes, that's all
No & additionally I'm not worried about making a spec for the Fall release without the implementation investigation. If when we go to implement Looking back at notes, we had a subteam with conclusions in #21888 (comment) . I was bringing up my uncertainty because these conclusions make room for the change to default yield intent we are discussing here; but it was not discussed there to do it as a spec-only change for the Fall release. @vasslitvinov - do you think it's OK to proceed with a spec change for the fall release, to say that iterators yield by |
Yes, this sounds good. |
I believe we came to a conclusion in the discussion of this, but I'm not remembering what it was. Does someone else remember, and can this issue be closed? |
[related to @vasslitvinov's #21888 as well as https://github.com/Cray/chapel-private/issues/5235]
While talking to @mppf yesterday, we got a bit tangled up in a question about when a loop iterator variable should / should not be mutable. For example, today, given the following loops:
i
isconst
/ immutable within the loop body (i.e., you couldn't writei += 1;
) whereasa
is mutable, referring to the elements of the array. This is the behavior I (strongly) believe we want, but the question that tripped us up a bit was "What causes it?"In my mind, the behavior has been that "any iterator whose yield intent has
const
nature will prevent the corresponding loop index variable from being modified." But now, I am less convinced that this is the case since, from a few experiments, it looks as though it might simply be "do we yield by default/out intent or not?"Specifically, if we apply a
const ref
intent to the following iterator:we're able to modify the loop index variable within the loop body, and doing so modifies the iterator variable, which doesn't match my intuition (i.e., the
const
seems to have no effect and gives the exact same behavior as yielding byref
, where I'd call the behavior expected):Meanwhile, default intent prevents modifications within the loop body as expected:
And, interestingly, so does
out
intent (which may or may not be OK, depending on whether we want to distinguish betweenout
andconst out
).Relating this to #21888, I believe our current thinking there is that the default yield iterator should be
const
, which I interpret as meaning "the implementation can choose betweenconst ref
or[const] out
", where I'd expect the impact in both cases to be that the loop index variable is immutable within the loop by default. I believe this supports the common case, reduces chances for surprise, and has the benefit of being symmetric with (most) default argument intents beingconst
.Based on that understanding, I think the two action items here are:
const ref
yield intent should prevent modification of the loop index variable back at the callsite, implying we have a bug in our implementation todayout
intent on an iterator should prevent modification of the loop index variable as it seems to today, or whether a distinctconst out
intent should imply that.out
implies immutability, the implication would seem to be that there is no way to create an iterator that copies values out resulting in a mutable loop index variable. That may be acceptable, but may require some education, since I don't believe it's obvious that that's whatout
implies from the name.out
shouldn't imply immutability of the loop index variable, that suggests to me that we should introduce aconst out
intent and haveconst
imply eitherconst out
orconst ref
on iterators. This opens up the question of whether we'd also want to support aconst out
intent on procedures and how it would behave differently from theout
intents we have today.The text was updated successfully, but these errors were encountered: