# Why does Array{Int}() product Array{Int, 0} and not Array{Int, 1}

When initializing an array variable (whose size is unknown at the time), I often write `a= Array{Int}()` or `r = Array{Rational}()`, and then get an error when I try to `push!` into it later (`no method matching push!(::Array{Rational,0}, ::Rational{Int64})`).

It seems to me that it will be convenient if the constructor for `Array{Int64,N} where N` assumed N was 1, instead of assuming it 0 when left unspecified. I realize I can always write `Array{Int, 1}` or even better, `Vector{Int}` instead, but this seems like it would be a convenient shorthand for the common case of wanting an 1-dimensional array, and more useful than getting back a 0-dimensional array.

But may be I’m missing the intent behind this, so is there a reason such a constructor call gives you back an `Array{T, 0}` and not `Array{T, 1}`?

The size of a zero-dimensional array is `()`. That is, it has no dimensions. Thus, when you call the `Array` constructor with no dimensions, it instantiates a zero-dimensional array.

To construct a zero-length vector with a similar sort of shorthand, you can use `Vector{Int}()` instead. Or you can pass one explicit zero-length dimension like `Array{Int}(0)` — but that’s changing to `Array{Int}(undef, 0)` in 0.7.

1 Like

You can also use something even simpler, `Int[]`, that’s what I always use.

1 Like

That kinda makes sense in linguistic terms, but in programming terms we usually don’t take missing argument x to mean x doesn’t exist or is 0, we usually expect it to have a sane default. To take a random example, when calling `isapprox(3.1415926535, π)`, isapprox doesn’t assume `rtol` should be zero because it’s left unspecified, it takes a reasonable default value appropriate to the usage. In my view, `Array{Int}` leaves the dimension parameter unspecified, and like any optional argument it should take the most commonly expected value as its default. It’s a minor thing, but little things like this add up to make the language easier to use.

I don’t necessarily agree that this would be the most commonly expected value. To me a rank-n array has n indices. If I do `Array{Int}(undef)` then I’ve specified the dimensions of 0 of them. Note also that you can use `Vector{Int}` as a constructor, but you will still need the `0`.

2 Likes

Your view is mistaken; as described in the docstring of `Array`,

`dims` may be a tuple or a series of integer arguments corresponding to the lengths in each dimension.

so `Array{Int}()` is provided a series of integer arguments, which just happens to be empty. This is nice and consistent with all the syntactic variations.

Arguing from expectations is always tricky, as they can be very heterogeneous depending on the programmers previous experience with other languages and language design. Enforcing consistency is much easier to implement.

1 Like

`Array{T}(dims...)` in general is a varargs constructor where the number of arguments determines the number of dimensions of the resulting array. How many arguments are there in the call `Array{T}()`?

Maybe `Vector{Int}()` or more generally `Array{Int,p}()` should create empty arrays of the specified shape. ( the latter case is actually convenient as `Array{Int,p}(zeros(Int,p)...)` is rather ugly.)

The `{}` syntax after types is usually used for type parameters, which don’t include `size` for an array, just `ndims`. So if you understand your suggestion correctly, it would be somewhat confusing.

Yes I know that. I’m saying that it creates an empty array of. The specified shape: `Array{T,2}()` would create a 0 x 0 matrix. (Or just look at the code example I gave for the general `p`).

I missed the use case for that, can you give an example? Eg `Vector{T}()` is used because I can `push!` into it, but I don’t think I have ever created an array with size `(0, 0, ..., 0)`.

Create an empty spot in a mutable struct to bind another array to later.

2 Likes

Thanks for the example. I am not sure if the use case warrants a function in `Base` though.

How is a zero-dimensional array ever useful?

I think this is the point at which a consistency argument comes in though.

It never occurred to me that Array{Int, 3}() wouldn’t create an empty 3-dimensional array until just now, given I’ve used Int[] and Vector{Int}() in the past. Not having the empty constructor is a completely unnecessary gotcha given Array{Int, 1}() and it’s obvious what it’ll do [and I can imagine using it exactly as @ChrisRackauckas suggests].

It throws an error because you didn’t give it 3 dimension arguments, to maintain consistency across all array constructors. The same goes for `Vector{Int}(undef)`.

I think I agree that `Array{T,N}(undef)` creating rank-`N` arrays with all 0 dimensions would be sensible, this is of course not the same thing as `Array{T}(undef)`, though I’m not sure I see many use cases for this. I always try pretty hard to allocate the entire array first and not push.

I think I agree that `Array{T,N}(undef)`

There’s no need for the `undef` if the array is empty: just `Array{T,N}()` would do. Note this is how it is even in Julia v0.7-alpha for `Vector`:

``````julia> Vector{Int}()
0-element Array{Int64,1}

julia> versioninfo
versioninfo (generic function with 4 methods)

julia> versioninfo()
Julia Version 0.7.0-alpha.20
Platform Info:
OS: macOS (x86_64-apple-darwin17.5.0)
CPU: Intel(R) Core(TM) i7-7700HQ CPU @ 2.80GHz
WORD_SIZE: 64
LIBM: libopenlibm
LLVM: libLLVM-6.0.0 (ORCJIT, skylake)
Environment:
JULIA_VERSION = 0.6
``````

Surprisingly, a zero dimensional array contains one entry:

``````julia> A = Array{Int,0}(undef)
0-dimensional Array{Int64,0}:
-1

julia> A = 4
4

julia> A
0-dimensional Array{Int64,0}:
4
``````

It’s very similar in usage to a `Ref`.

EDIT: This syntax makes the behaviour less bizarre looking:

``````julia> A[] = 3
3

julia> A
0-dimensional Array{Int64,0}:
3
``````
3 Likes

There is definitely a trade-off being made between “consistency” and convenience here. How you see the consistency depends upon your viewpoint. I would argue that the `Vector{Int}()` constructor is the odd-one out and leads to this misunderstanding.

That said, now that we have the `undef` constructors in 0.7, the status is slightly different. No longer is `Array{Int}()` the logical extension of:

``````Array{Int}(dim1, dim2) -> Array{Int}(dim1) -> Array{Int}() # 2 -> 1 -> 0 dimensions
``````

``````Array{Int}(undef, dim1, dim2) -> Array{Int}(undef, dim1) -> Array{Int}(undef)
``````

So, then, there remain a few questions:

• What should `Array{Int}()` mean? I’d argue it should be an error — indeed it’s deprecated on 0.7 in favor of `Array{Int}(undef)` and will be a method error on 1.0. That seems sensible.
• What should `Array{Int, N}()` mean? This is deprecated for `N = 0` and is an error for all `N > 1`. When we last considered this, however, we didn’t have the new `undef`-ness. So now I suppose it’s no longer clashing with the above vararg dimensions, and could be taken to mean an “empty array.” We’d probably have to leave it as an error for `N=0` since you cannot have an empty 0-dimensional array. It somewhat makes sense that you wouldn’t need to specify `undef` for an empty array, but I’m still not entirely convinced this is a good idea. The good news is, however, that all of this design for 1.0 is erring slightly conservative and these would all be new features that could be introduced in 1.x.
5 Likes

It’s useful as a type stable default argument value. Assuming for a moment that `Array{T, N}()` would create an empty array.

``````function f(x::Array{T, N}, weight = Array{T, N}()) where {T, N}
if isempty(weight)
# Compute unweighted special case.
else
# Compute the general case.
end
end
``````

Yes, you could use `nothing` to indicate no weight and being a small union it would probably be fast, but full type stability is still an attractive property.