Vector vs n-element Vector?! Can push to one but not the other

Why is there a difference between a & b? Why can I push to a but not b?

julia> a = Real [ ]
Real [ ]

julia> push!(a, 0.0)
1-element Vector{Real}:
0.0

julia> b = Vector{Real}
Vector{Real} (alias for Array{Real, 1})

julia> push!(b, 0.0)
ERROR: MethodError: no method matching push!(::Type{Vector{Real}}, ::Float64)
Closest candidates are:
push!(::Any, ::Any, ::Any) at abstractarray.jl:3059
push!(::Any, ::Any, ::Any, ::Any…) at abstractarray.jl:3060
push!(::AbstractChannel, ::Any) at channels.jl:10

The type of a is a “1-element Vector{Real}”, whereas b is “Vector{Real} (alias for Array{Real, 1})”.

I understand that there is no clear way to push to a matrix, and so the latter issue makes sense, but what’s up with a? Why call it a vector, when it acts like a list? Wouldn’t the “1-element” part of the name imply that the size is fixed? The reality is the opposite.

Vector{Real} is a type, not an instance of that type. To construct an empty instance, use the zero-argument constructor.

julia> b = Vector{Real}()
Real[]

julia> push!(b, 0.0)
1-element Vector{Real}:
 0.0

Unlike in Matlab, a vector is not a matrix: the former has one dimension, the latter has two.

julia> Vector <: AbstractMatrix
false

…and a Vector’s length isn’t encoded in its type signature and is free to vary. StaticArrays.jl provides fixed-length vectors.

julia> dump(ones(3))
Array{Float64}((3,)) [1.0, 1.0, 1.0]

julia> dump(@SVector ones(3))
SVector{3, Float64}
  data: Tuple{Float64, Float64, Float64}
    1: Float64 1.0
    2: Float64 1.0
    3: Float64 1.0
6 Likes

You have explained it very well. Thanks. I am only a casual Julia programmer - so coming from other languages - I’m used to the “new” keyword to trigger the constructor. Without it - I see that I am only making an alias for the type.
So, generally the parameterless constructor is a function and requires the “()” for it to be invoked. Correct?

Constructors are just Julia functions and can, in principle, take any number of arguments. Multiple dispatch is used to resolve which type is constructed.

julia> methods(Vector)
# 5 methods for type constructor:
[1] (Vector)() in Base at baseext.jl:38
[2] (Vector)(x::SparseArrays.AbstractSparseVector{Tv}) where Tv in SparseArrays at C:\Users\alexa\.julia\juliaup\julia-1.8.2+0.x64\share\julia\stdlib\v1.8\SparseArrays\src\sparsevector.jl:947
[3] (Vector)(D::SuiteSparse.CHOLMOD.Dense{T}) where T in SuiteSparse.CHOLMOD at C:\Users\alexa\.julia\juliaup\julia-1.8.2+0.x64\share\julia\stdlib\v1.8\SuiteSparse\src\cholmod.jl:856
[4] (Array{T, N} where T)(x::AbstractArray{S, N}) where {S, N} in Core at boot.jl:481
[5] (Vector)(::UndefInitializer, m::Integer) in Base at baseext.jl:32

There’s special syntax for parameterless array constructors, but everything else should take the T() form.

I just want to point out to the OP that most of the time you don’t want to use Vector{Real} because Real is an abstract type. Your vector a as you’ve defined it can hold any combination of Float64, Integer, Float32, etc. values and this comes at a cost in performance:

julia> x1 = Real[1.0 for _ in 1:1000];

julia> x2 = Float64[1.0 for _ in 1:1000];  # or just use ones(1000)

julia> using BenchmarkTools

julia> @btime reverse!($x1);
  880.952 ns (0 allocations: 0 bytes)

julia> @btime reverse!($x2);
  222.925 ns (0 allocations: 0 bytes)
6 Likes

Interesting that reverse! is also slower for Real, it shouldn’t have to be because for that operation you just need to reverse the list of pointers which are the same size as Float64 and stored contiguously as well. You don’t have allocations here which would happen if you stored any new values that need to be boxed somewhere on the heap. So it’s not the best example for showing that abstract element type containers are bad :slight_smile:

I’m happy to learn about performance improvements with better typing. I will try to keep that in mind. But, if I may return to the original problem that I faced.
The feedback I received from REPL (or even in the variable watcher in VS code) could be clearer. Who do I suggest this to?
The response for a is “Real [ ]” - it’d be more informative (although perhaps a little verbose) to say “Object of type Real [ ]”
or better yet for b…instead of reporting “Vector{Real}”, it should be “DataType Vector{Real}”
Of course, I could have debugged before my post by using “typeof()” and seeing that b is a DataType. but shouldn’t the responses in REPL just say this.
In VS Code variable watcher, it shows a as “Vector{Real} with 0 elements” and b as “Vector{Real}”. That’s confusing right. Let’s change it to “DataType Vector{Real}”

2 Likes

Object of type Real[] would not be accurate because the type is Vector{Real}, Real[] is just shortcut syntax for Vector{Real}() (note the parentheses).

I guess you kind of get used to it over time that instances of values usually have (...) in their printout because the representation looks like the instantiation syntax by default. That’s also the main reason against your suggestions like DataType Vector{Real}. It’s a feature that many values print in a way such that you can copy paste the printed representation, execute it, and get another object like that. DataType Vector{Real} would break that.

1 Like

sorry, but where are you seeing the parentheses when interrogating a variable?

Yes, I’m pretty sure we don’t want parentheses when reporting the variable type. That would be referring to the function/constructor. For example, one could do this

c = methods(Vector{Real})[1]
Vector{T}() where T in Core at boot.jl:478

Interestingly, when you interrogate with “typeof”, you simply get MethodList.

julia> typeof(c)
Base.MethodList

This further convinces me what is reported in the REPL listener and for VS variable watcher should match “typeof(b)”,that is, it should be “DataType” not “Vector{Real}”

I said the opposite, types don’t print with parentheses, instances of those types often do if that’s how you usually instantiate them.

This is the default representation of objects:

julia> struct A{T}
           x::T
       end

julia> A(4)
A{Int64}(4)

An example from Base:

julia> Ref(3)
Base.RefValue{Int64}(3)

(here, Ref is an abstract type, but still has constructors which construct objects of an appropriate subtype)