Why is splatting a non-iteratable allowed?

Why is this allowed?

julia> [1.0..., 2.0, 3.0]
3-element Array{Float64,1}:
 1.0
 2.0
 3.0

It strikes me as non-intuitive, and has already caused me some bugs.

It’s especially weird when you think about it relative to how it operates relative to other splats:

julia> [1.0..., 2.0, 3.0]
3-element Array{Float64,1}:
 1.0
 2.0
 3.0

julia> [[1.0]..., 2.0, 3.0]
3-element Array{Float64,1}:
 1.0
 2.0
 3.0

julia> [[[1.0]]..., 2.0, 3.0]
3-element Array{Any,1}:
  [1.0]
 2.0
 3.0

julia> [[[[1.0]]]..., 2.0, 3.0]
3-element Array{Any,1}:
  [[1.0]]
 2.0
 3.0

Numbers are iterable.

It’s a complicated topic, and there have been many discussions about whether or not numbers should be iterable. See e.g. https://github.com/JuliaLang/julia/issues/7903, https://github.com/JuliaLang/julia/pull/19700, and others.

9 Likes

Is there any hope that we can be actually be rid of iterable numbers? It seems like people keep opening issues and PRs to solve this bug, yet it remains.

Personally, I think it’s nice that numbers are 0 dimensional iterable containers. Makes a lot of sense to me. :man_shrugging:

julia> 1[]
1

julia> [i for i in 1]
0-dimensional Array{Int64,0}:
1

julia> collect(1)
0-dimensional Array{Int64,0}:
1
5 Likes

The problem is every time someone tries, they become convinced halfway through fixing it that it makes Base code enough uglier that maybe it isn’t such a good idea.

7 Likes

23 Likes

I feel seen.

2 Likes

I understand that in some fields there is little distinction between vectors and scalars, but this strikes me as a MATLAB-esque anti-pattern. At the very least it’s a highly unorthodox decision from a PLT perspective. I’m curious what an actual use case of this looks like in Base?

1 Like

Some examples here: make numbers non-iterable? · Issue #7903 · JuliaLang/julia · GitHub

This is known, and arguably it is not the best decision that was made in designing Julia, but the earliest this can be changed is 2.0 since the change is breaking. And then the benefit in purity may not be worth the breakage and the work. We shall see.

If you really care about this, the best way to convince people is not arguing from a PL design perspective since that is flogging a dead horse, but by making a concrete PR that demonstrates the change.

7 Likes

I think the last comment from @stevengj on that thread is particularly on-point:

My experience in trying to implement even a small piece of this pre-1.0 (#7903) leads me to believe that changing this would lead to a huge amount of code churn over the whole ecosystem. i.e. it wouldn’t be worth it without huge benefits, which I haven’t seen anyone articulate beyond “slightly confusing to some newcomers”.

9 Likes

So it sounds like Julia v2.0 is the best shot for cleaning this up. I’d like to avoid dead-horse-beating; this was just my first reaction as a newcomer to the language. I understand every language has its warts, just a matter of improving over time, etc etc.

1 Like

I also find iterable scalars to be odd, but I think you’ll find differing opinions on whether this is a “wart”. Often there is a balance between convenience and purity.

5 Likes

Making numbers iterable does not just decrease “purity”, but also hurts generality and extensibility. As a simple example - there is code that depends on the “dims” argument to various functions to be iterable. This works for dims=[1, 2, 3], works for dims=1, but for arrays with named dimensions dims=:dimension_a won’t work.
Basically, this decision just means that numbers are special-cased for little (apparent major) reason.

3 Likes

I don’t think the derogatory comparison to Matlab is really compelling here. In Matlab, scalars and vectors are 1x1 and nx1 matrices respectively. In julia we have a quite powerful type system and multiple dispatch where scalars, vectors, covectors, matrices and higher dimensional structures are all distinct and distinguishable at compile time. Numbers are not arrays and arrays do not have to contain numbers.

I guess what I’d have to say about things like indexing, iterating or splatting numbers is: what else would it mean than what it means now?

I think it’s a fairly unambiguous behaviour, even if it is a little surprising to new users.

10 Likes

An error! I think there’s good reason to make this change, but I’m firmly in the camp of it being too much churn for not enough benefit. I’d be very happy to be convinced otherwise, though.

8 Likes

How does this relate to the concept of empty sums or empty products, if at all?

1 Like

I guess I’d just say that we should save errors for when there’s not a clear meaning of something. Hence, I think it makes sense to throw an error if I do

struct Foo end
[Foo()...]

because we don’t know what sort of thing the programmer intended Foo to be.

On the other hand, its pretty uncontroversial to consider Numbers to be scalars. To me, if I asked “how many dimensions does a scalar have?” The answer is “0”, not “undefined”.

For me, once I can accept that numbers have a well defined number of dimensions or axes (I.e. 0), then it seems that the indexing and iterating behaviour just comes along for the ride because we already went through all the work elsewhere of defining the behaviour of zero dimensional objects.

6 Likes

But the issue here is that the effective dimensionality of a number in Julia is currently 1, not 0:

julia> length([5...])
1
julia> length(5)
1

But even then, I don’t think the idea of a non-container type having a length makes all that much sense…

Length is not dimensionality.

julia> ndims(5)
0

julia> size(5)
()

julia> axes(5)
()

It has zero dimensions and one element, so the length is one.

14 Likes