Understanding typing rules

Hi,

In the following expression

using StaticArrays
using Random
Random.seed(42)

a = SVector{2, Float64}(1.0, 2) 
b = a + rand(2)

I was initially (incorrectly) expecting that b’s type would be SVector{2, Float64}.
Instead, it’s SizedVector{2, Float64, Vector{Float64}}.

Debugging with @code_warntype, rand(2) is a Vector{Float} (though a compile time constant should allow it to pick SizedVector).

Debugging a bit more, I thought that the typing rules then use the intersection of types (typeintersect(typeof(a), typeof(rand(2)) to determine the type of the expression, but it seems I’m wrong there, as that intersection is Union{}.

I know that I can force (or be explicit) in my intent

b = a + rand(SVector{2, Float64}) # both parts of the expression are identical type, so no issues

Then, the plot thickended, because this

c = rand(2) + a # SVector, not SizedVector

actually does what I initially expected.
So this suggests the type is determined by R/L order of evaluation?

My questions are these:

  1. What are the typing rules that determine typeof(b)? How, for future cases, could I check? I know code_warntype gives me the outcome and intermediate steps, but not the rules (so I can avoid the misaligned expectation).

  2. rand(2) is implemented to give a Vector, so later invocations can push!() on it, that makes sense. However, then why would the expression a + rand(2) change to SizedVector? Initially I thought it used the intersection rules, but I seem to be incorrect about that. (note, from performance perspective, naturally it’s the right thing to do SizedVector here, though SVector would be ideal). It’s just that I can’t figure out how the rules work.

To explain:

a = SVector{2,Float64}(2, 3.0)

b = a + rand(2) # SizedVector

push!(b, 2) # Error, as it's static sized

c = rand(2)  # Vector

push!(c, 2) # ok

d = rand(2) + a #SVector, not SizedVector
push!(d, 3.0) # Fails, as expected

I tried the above in a function, but it makes no difference, so it’s not a question of global scope affecting the outcome (unless I made a mistake somewhere).

In summary, where can I find how the type rules affect type inference (and is this dynamic, or compile time) for such expressions?

Any help understanding this will be greatly appreciated,

I can’t reproduce this. (Specifically, I don’t get any SizedVector in your initial example.)

What versions of Julia and StaticArrays are you using? Do you do something more in your session? Do you have something interesting in your startup.jl?

What does this mean?

You’re right, looks like my sessions was the cause, a clean session fixed the issue, apologies for the noise

A typo somewhere, you get SizedVector if you used broadcasted addition instead a .+ rand(2). StaticArrays mostly defer to the other input for computing an output type for broadcasting:

BroadcastStyle(::StaticArrayStyle{M}, ::DefaultArrayStyle{N}) where {M,N} =
    DefaultArrayStyle(Val(max(M, N)))

But eventually the special StaticArrays axes, if preserved perfectly, wraps the output array into the statically SizedArray wrapper:

julia> similar(Array{Float64}, (SOneTo(2),))
2-element SizedVector{2, Float64, Vector{Float64}} with indices SOneTo(2):
 6.95242594095396e-310
 6.95242594094763e-310

A SVector result was possible with Vector, but a mutable output was chosen to be consistent with the mutable outputs of broadcasting with higher-dimensional Arrays e.g. a .+ reshape(1:100, 1, 100) becomes 2x100, a size known only at runtime.

Direct + dispatches to a different method that can more reasonably choose to instantiate an SArray of the same size (indeed a compile time constant) because there is no broadcasting to change sizes; a + reshape(1:100, 1, 100) would not work. map(+, a, [1, 2]) makes the same choice and is implemented by a StaticArrays method.

1 Like

Ah, thank you, I was thinking I had just imagined the whole thing even though the repl output was still in my terminal’s screen, I attributed it to a mistake on my part.
Thanks for the explanation!