Zero-dimensional arrays are acting strange

I can can create a zero dimensional array of Nothing:

julia> Array{Nothing, 0}(nothing)
0-dimensional Array{Nothing, 0}:

but not other types:

julia> Array{String, 0}("")
ERROR: MethodError: no method matching Array{String, 0}(::String)

julia> Array{Tuple{Nothing}, 0}((nothing,))
ERROR: MethodError: no method matching Array{Tuple{Nothing}, 0}(::Tuple{Nothing})

julia> Array{Vector, 0}([])
ERROR: MethodError: no method matching Array{Vector, 0}(::Vector{Any})

I can crete a zero dimensional array of Union{Nothing, Float64} with nothing and then update the value in it to Float64.

julia> x = Array{Union{Nothing, Float64}, 0}(nothing)
0-dimensional Array{Union{Nothing, Float64}, 0}:

julia> x[] = 1.1

But I cannot initialize the array with a Float64.

julia> x = Array{Union{Nothing, Float64}, 0}(1.2)
ERROR: MethodError: no method matching Array{Union{Nothing, Float64}, 0}(::Float64)

Can someone tell me what’s going on? I don’t know computer science.

The issue here is that this method isn’t for initializing an array with values. It’s only for initializing an array with an “initializer” object, which can be one of undef, nothing or missing, telling Array how to initialize the array initially. This is documented in the docstring of the Array constructor. If you want a zero-dimensional array from a value, you can ues the fill function with a single argument:

julia> fill(1.1)
0-dimensional Array{Float64, 0}:

Why do these special cases missing/nothing exist?

Array{Union{Float32,Missing}}(missing, 1,3) is (roughly?) a special syntax for fill!(Array{Union{Float32,Missing}}(undef, 1,3), missing), but the docs don’t really offer a reason to treat these two specially. Is there some optimsation these allow? Trying to time them doesn’t reveal much.

Presumably they cannot be removed before 2.0, but can they be marked as weird discouraged special cases? With a pointer to fill or fill!(... undef ... as the generic thing?

1 Like

I see, so this is nothing specific to zero-dimensional arrays. It’s true for Arrays generally.

I saw a discussion elsewhere too, but this means that if you want to initialize an array that takes, say Int64, you’ll need to actually create an array that accepts Union{Nothing, Int64}. Seems clunky and confusing, but what do I know?

Also confusing because Array{Int64, 1}() works but Array{Int64, 0}() and Array{Int64, 2}() don’t.

No, you don’t. You can write fill(1, 2, 3) or fill!(Array{Int}(undef, 2, 3), 1). These are the same for all types.

The nothing/missing cases are weird confusing extras.

Array{Int64, 1}() works

This seems to be another weird confusing edge case. (And undocumented?) This array is empty, so perhaps the logic is that the constructor need not care whether you asked for it uninitialised or what. Array{Int64, 1}(undef, 0) would be more standard, or just Int[]. Array{Int64, 2}() does not work; I suppose there are many sizes an empty matrix could have, like fill(99, 0, 1) or fill(99, 3, 0).

@mcabbott I should have said “to initialize a placeholder array that later takes Int64”. I understand that fill works if you already know what you want to put in the array.

I think this is usually called an uninitialised array – you create the array, but don’t initialise its values. This is what undef is for. (And similar.) You should not touch missing/nothing unless you have reason to store those objects in your array.

1 Like

I guess it makes sense that it’s called “uninitialized”. I am not familiar with computer science terminology. But thanks for the note on undef.

For anyone interested, here’s some earlier discussion on a similar topic. People smarter than I also tend to think that behavior of Array{Int64, 1}(), Array{Int64, 0}(), Array{Int64, 2}() is inconsistent.

Here is a long discussion on how #undef came about.