Maybe this’ll be helpful to new users like myself. I’ve been writing Julia every day for about a year now. (and still considering myself new because I still haven’t had the time to learn the type system properly, for example), but I’m still getting bitten by this every once in a while.
Take a look at this code:
const AM = AbstractMatrix{T} where T
function expectations(P::AM{<:Real}, M::AM{<:Real}, V::AM{<:Real})
a = @views sum(
@. P[k, :] * M[k, :]
for k ∈ size(P, 1)
)
sigma = @views sum(
@. P[k, :] * sqrt(V[k, :])
for k ∈ size(P, 1)
)
(; a, sigma)
end;
This code is supposed to compute expectations of some random variables for each row in the matrices P
(each column is a discrete probability distribution), M
and V
.
To me, it looks completely fine, and it also runs and outputs something that looks reasonable… sometimes.
Other times, however, it randomly returns some garbage that makes zero sense, depending on the order of rows in the input matrices, even though it’s supposed to be summing over them, so the order shouldn’t matter! Guess why?
Because for k ∈ size(P, 1)
does not iterate over a range from one to size(P, 1)
, as I intended!!! In fact, it iterates over one single integer size(P, 1)
, which boils down to selecting just one last row of each matrix instead of summing over all rows. That’s super confusing!
Why would iterating over an integer (as opposed to an array or a tuple of one integer) work at all without producing any errors?! I guess it works like this so that code like Int(2.0)
and Int.(2.0)
(notice broadcasting) produces the same result. So, this kind of makes sense, but it also just made me waste several hours (seriously) debugging my code and surrounding libraries because I was absolutely sure I most definitely couldn’t have messed up this simple for
loop.
Yet it was exactly what I messed up.