-
-
Notifications
You must be signed in to change notification settings - Fork 5.5k
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
Add support for zero(::Array{Array}) #8759
Conversation
The problem with the existing definition is that it tries to call zero(::Type{Array}), which fails because it doesn't know what size the eltype should be. The solution here is simple - if the outer Array is not empty, call zero on its first element. A similar problem occurs with one() but that is harder to fix.
-1 It's not obvious to me that
|
Could this be considered a bug in
|
@timholy, would this be a horrible change? #multidimensional.jl:174-9
@ngenerate N typeof(A) function fill!{T,N}(A::AbstractArray{T,N}, x)
@nloops N i A begin
@inbounds (@nref N A i) = x #should this be deepcopy(x)?
end
A
end |
To quote @StefanKarpinski it would be "bizarre", (if I understand correctly what he is talking about). I'm not sure I agree with that statement though. |
I'm puzzled. I don't really see a use case for the current behavior of |
Or maybe even |
To continue the second-guessing of @StefanKarpinski,
sounds to me like |
@andreasnoack that is a good point, the proposed change should be |
I think this is a question for others to answer; I just "Cartesian-ified" an existing implementation of This seems to be one of those "what do you mean by |
The same issue exists when providing a default value for a fill!(B, ()->zeros(2,2)) The downside (here and in fill!(B, ()->(()->zeros(2,2)))) |
Is it ever useful to fill an Array with the same reference in all elements? Med venlig hilsen Andreas Noack 2014-10-22 9:56 GMT-04:00 Kevin Squire notifications@github.com:
|
Sure: black = fill(RGB(0,0,0), 5, 5)
red = fill(RGB(1,0,0), 5, 5)
checkerboard = Array(Matrix{RGB}, 9, 9)
fill!(checkerboard, black)
for i = 1:2:length(checkerboard)
checkerboard[i] = red
end |
It doesn't look like the checkerboard example would break if we changed |
Throwing down the gauntlet, eh? (I'm not saying changing it would be wrong on average, but I do feel that whatever we choose will be wrong sometimes.) component1 = [0 0 0; 1 1 1; 0 0 0]
component2 = [0 1 0; 0 1 0; 0 1 0]
img = rand(27, 27)
tiles = fill(component1, 9, 9)
for i = 1:2:27 tiles[i] = component2; end
for i = 1:iter
# improve component1, component2
nmf(...)
# choose the best component to describe the ith tile
tiles[i] = err1 < err2 ? component1 : component2
end |
Adding implicit deepcopy anywhere seems like a bad idea – I'd prefer to know when I'm about to make a complete copy of everything in the entire world. Since deepcopy is a no-op on immutable that don't reference mutables, I'm not super concerned about that issue (ints, floats, RGB, etc.). The thing is that the behavior people really want here is probably for the expression that generates the value to be evaluated many times, not for the value to be copied many times. For example, if someone writes fill!(Array(10), rand()) they probably wanted to call |
It doesn't seem to be the case julia> @time for i = 1:10^7;copy(1);end julia> @time for i = 1:10^7;deepcopy(1);end Med venlig hilsen Andreas Noack 2014-10-22 10:58 GMT-04:00 Stefan Karpinski notifications@github.com:
|
Ah, yes. Because deepcopy creates a dict to keep track of circularities. It's still a no-op, just a very expensive one. Good ol' deepcopy. |
@timholy just to clarify, the very issue I am wrestling with is in loops of the form fill!(tiles, startingtile)
for i = 1:iter
dostuff()
tiles[i][j] = newval #I want to update the jth entry of the ith element but this doesn't do it
end If the inner loop were to do
I see the issue; it happens to make no difference for my particular use case. However, I still see no justification for the current semantics of |
The system should not try to guess what needs to be copied, or how you're going to mutate things. Generic library functions can do what they want, and mutation has to be managed manually. |
The current |
@jiahao, the point with my example was intended to be that you want to maintain just 2 components to describe all the tiles, so each element of the array must maintain a pointer to one or the other. Sorry if that wasn't clear. I'm not saying there aren't good reasons to want the other thing sometimes, too. Just that there are use cases for the current behavior. |
So my question is, is this a premature optimization? Should It would be very sad if we go for the latter. It will mean that practically all the generic linear algebra code will get uglier because we will have to special case whether or not the element is mutable. |
IIUC then I should see that there are only two unique pointer addresses in the
or am I misunderstanding what |
Why would linear algebra code mutate the elements of a matrix? Seems odd to me. The problem is that once you get into stuffing implicit |
(I assume you meant elements of elements of a matrix.) This comes up every time you want to write in-place updating operations on tiled matrices (i.e. a matrix of matrices) which would overwrite the elements of the outer matrix with new elements (inner matrices). Usually we don't want out-to-place updating operations where you allocate new inner matrices, populate them and then reassign then as elements of the outer matrices, which would be the only thing you can do with immutable semantics. |
@jiahao, bug in my example (non-concrete element declaration). Change to |
@timholy Ah yes much better. I was beginning to go crazy for Not Getting The Point. |
Another way to say it: you can't expect all issues surrounding mutation to be solved for you by adding a few You're describing a function whose preconditions include "all inner matrices must be distinct objects". I'd argue that this isn't a reasonable precondition, because it's very hard for a caller to ensure. That use of mutation is an implementation detail of the function, so the function needs to bear full responsibility, not force it on callers. |
Isn't functional programming all about purity of cc: @jakebolewski |
Yes, you're misunderstanding. Shared references are not "impure". Mutation is what's impure. |
Predictably, I'm 100% with Jeff on this one (or is he 100% with me – after all, I called it bizarre first ;-) – it's easier to ask for a copy when you want one than to prevent a copy when you don't need one. |
I will agree that copying behind your back is generally not a good thing. I think I've finally zeroed in (as it were) on what is so confusing for me. It's the fact that the term 'value' in the description of the function
means "compiler value", which if boxed is a pointer value, but if unboxed is a value much closer to what I think about as "numeric value". |
Thanks everyone for humoring me as I stumble through yet another obvious-in-retrospect trajectory. |
These conversations are well worth having since a non-negligible amount of the time they lead to a positive change in the language and usually lead to more clarity for the people having the discussion. |
Real non-allocating linear algebra routines have two exclamation points. Much more impressive that way. But more seriously, if one thinks about writing for j = 1:J, i = 1:I
s = 0.0
for k = 1:K
s += A[i,k]*B[k,j]
end
C[i,j] = s
end with tmp = similar(C[1,1])
for j = 1:J, i = 1:I
c = C[i,j] # C is pre-filled with zeros-matrices
for k = 1:K
A_mul_B!(tmp, A[i,k], B[k,j])
add!(c, tmp)
end
end If you tried to make this generic so it works for either immutable |
(Didn't hit refresh on the browser and so didn't realize the conversation had taken a different turn.) |
Just saw @amitmurthy publish code to julia-users with this exact mistake. Do we really expect our average user to have such a different intuition of what |
Thanks @ivarne , corrected the mistake on julia-users. |
But that is not a mistake. Any objection to this is an objection to mutation. It would be better to get rid of mutation than to have to randomly copy things everywhere for no clear reason. |
How can that use of |
That use of |
I stumbled on the same behavior when creating an array of set a = fill(Set{Int}(), 2)
push!(a[1], 1)
a
#2-element Array{Set{Int64},1}:
# Set{Int64}({1})
# Set{Int64}({1}) I think the current behavior will lead to unpleasant experiences / silent errors in user codes. |
I used
This may be linked to #9147 |
The problem with the existing definition is that it tries to call zero(::Type{Array}), which fails because it doesn't know what size the eltype should be.
The solution here is simple - if the outer Array is not empty, call zero on its first element.
A similar problem occurs with one() but that is harder to fix.