A = ones(3,3);
# this works
A[1,1] = 2;
2.0 1.0 1.0
1.0 1.0 1.0
1.0 1.0 1.0
# but this does not
A[:,1] = 3;
2.0 1.0 1.0
1.0 1.0 1.0
1.0 1.0 1.0
why is A not modified in this instance? I realize that A[:,1] is creating a copy of A[:,1] and then I’m indexing into that copy, for instance I can get it to work by specifying view(A[:,1])
The tldr is that the decision to make A[:, 1] a copy rather than a view was made back in 0.6 and was made due to the cost of views in 0.6 that was largely removed in 1.5 due to a smarter compiler. If we were making the choice today, we probably would do it differently.
I think it’s pretty risky to go down the path of breaking the rules of how the language works and try to second-guess the user: “You wrote this, but probably meant something else. Let me fix that, quietly, behind your back.”
For those who know that slices make copies, it would be very unintuitive to see that happen.
I guess my point in this case is that from a purely syntax perspective what the user wants is not ambiguous… there is no guessing needed. If the nested index appears on the left hand side of the = then user is trying to modify the variable on the left hand side. Is there a case where this break?
It could be for testing purposes or experimentation or demonstration. I don’t know.
But I think the suggestion is a really bad principle to follow, twisting or even outright changing the meaning of the code in order to guess what is ‘really meant’. I would guess it makes work harder for the compiler too, and as far as I understand it also breaks referential transparency.
And it feels super weird and dangerous. How are you going to be able to look at code and reason about what it does, when basic rules are broken like that?
In cases like this, you should get help to fix the code, for example by a linter, not have the code secretly fixed for you.
I’d not put things in such strong terms. This sounds more like a set of rules, where indexing on the left-hand side is always wrapped in @views. Currently, a view is created in certain cases while not in others, e.g.
a[:,1] .= 2
will not compute a[:,1] and then overwrite it with 2, rather it will compute @view(a[:,1]) and overwrite it with 2. In your words, this is already trying to guess what is really meant, rather than being consistent in how getindex works. That’s because this is lowered to dotview, and not getindex:
Sorry, perhaps I shouldn’t have used the word “implicitly,” as ultimately whether something fits that descriptor is a function of whether it’s documented—which this is.
Putting myself back in the shoes of someone freshly learning this aspect of the language, though, it’s not clear that accessing a slice should create a copy. Infact, it’s counterintuitive because it seems inconsistent.
Namely, getindex on a slice creates a copy, while setindex! (quite obviously) doesn’t. While this is straightforward when you consider the lowered code, from the syntax it’s less clear. From the syntax, it’s reasonable to believe slicing on the LHS of = simply doesn’t create copies while on the RHS it does. For the appropriate response to a newbie to be, “Well, first you must understand what this lowers to” is mildly frustrating, because we don’t interact with the lowered code—we interact with the syntax. (and why should an economist or a biologist have to know what lowering is, anyway?) What would have created a stronger sense of consistency would be if neither creates a copy, and both access slices of the array itself, so that nothing would seem different about appearing on the LHS vs. the RHS.
It seems as if the most direct way to resolve this inconsistency would be to make all sliced getindex calls return views, and to require copying to be performed by explicit calls to copy, as proposed in #3701. If that decision had been made, probably many aspects of the language would be quite different, and ripping the bandaid off now will be twenty times more painful than back then. Of course, it’s easy for me to Monday morning quarterback a decade after the fact; I’m sure there were hundreds of other considerations at play back then that have changed by now.
The fact that this uses internal machinery that with the name view in it is entirely beside the point. The basic idea all stems from the concept that a[...] = x will modify a and not the object stored at a[...].
The most frustrating optima are those that we know about, but are just out of reach
Many people have brought up the possibility of view-slicing arrays for 2.0, but it seems increasingly unreachable. The possibility of full-Julia arrays has also been raised many times. Maybe if the two ideas are merged—make the full-Julia arrays view-slicing so that that becomes the preferred idiom—the transition to view-slicing arrays in 2.0 can be easier.
Thanks, the rules are certainly clear and consistent. The confusion stems from the fact that a[:,:] = 3 is a compound getindex+setindex! operation, which makes this out-of-place. In an operation like this, one may split this into two statements:
b = a[:,:];
b = 3
However, this is not what one may do in a setindex!, despite the superficial similarity in syntax:
a[:] .= 3
is not the same as
b = a[:]
b .= 3
There is no contradiction here, just some getting-used-to.