Why is splatting a non-iteratable allowed?

Ah, so this is even more confusing than I first realized! Although I’m sympathetic to more advanced use cases the fact that so many newcomers to the language are tripping over this reminds me of a famous Donald Norman quote:

Change the attitude toward errors. Think of an object’s user as attempting to do a task, getting there by imperfect approximations. Don’t think of the user as making errors; think of the actions as approximations of what is desired.

Through participating in this thread, I’m beginning to feel as though I need to read a manual just to be able to using floating point numbers in Julia!

1 Like

Two things:

  1. Nothing of this applies to floating numbers specifically, it applies to all Numbers.
  2. I really recommend anyone in any language that has floating numbers and that will make use of them to read a manual about floating point numbers. There is so much misconception around them. I say that because I was one of those programmers that were never sure if their computations would be safe to do using floating point numbers.
5 Likes

And:

  1. You did post in #dev, not #user. The answers to why are always going to be deeper and more complicated than the answers to how. You definitely don’t need to know this to know how to use numbers. :slight_smile:
5 Likes

Ah, yeah floating point numbers were a dangerous example! Indeed I’m familiar with all the usual vagaries of floating point numbers. Really the point I was hoping to make here was the relative complexity of number types in Julia for a user that already understands their machine representations and so on.

And apologies if this was not the appropriate channel! I figured this fell more into the realm of language design discussion than user help type thing.

3 Likes

One thing I feel I should mention is that I’d be much more sympathetic of your view here @Samuel_Ainsworth if the iteration behaviour of numbers were something ad-hoc in julia. However, the way things like iteration and multi-dimensional indexing were defined is actually something that emerged out of a small set of simple, powerful principals.

It’s very common in things like mathematics or logical systems that when you start with a small set of general principals, surprising consequences of those principals emerge.

For me, I find the principals behind the iteration and multidimensional indexing protocols to be very compelling, so I’m quite biased to make room in my worldview for surprising consequences that come out of these principals.

If we were just winging it and doing these things on a case by case basis, I probably wouldn’t have chosen to have numbers be iterable or indexable. But taken in the wider context of the rest of julia, I think the behaviour has a an appealing and useful consistency. Changing it would weaken the principals that drive iteration and indexing in Julia.

8 Likes

I think the only really tricky thing here is that numbers behave like immutable zero-dimensional arrays, as opposed to 1-element vectors or 1×1 matrices. This is the right kind of array for a scalar to behave as, but it’s a little unintuitive that a zero-dimensional array has one element, which is because the product of zero things is 1.

11 Likes

From a math perspective, possibly yes.

But for generic programming, it is problematic if an API extension replacing numbers with something else would be feasible (there are many examples of this in the Julia ecosystem), but the implementation turns out to rely on the fact that they are iterable.

One of the key ideas that make Julia powerful is that very few things are “special”. Numbers aren’t, in two ways:

  1. the user could implement their own, equally efficient numerical type,

  2. arrays and collections of numbers have no distinguished role, other element types are equally efficient (provided they are bits types etc)

The only exception is that they are iterable collections of themselves. So I do think that this is rather ad hoc.

But they don’t really — that would require 1 isa AbstractArray{Int,0}, to be consistent with the rest of the language. Numbers behave like this for the purposes of the iteration protocol, but not otherwise.

I don’t think that this is a major wart in Julia, and I can see that is may have been appealing from a certain perspective. But in retrospect, I don’t think this turned to be a fortunate design choice — which is fine, designing semantics for a language is really hard to get right even after multiple iterations.

I think we should just admit that and try to change it. When 2.0 is on the horizon, I will be tempted to take a stab at fixing this (but I am really hoping that someone beats me to it :wink:).

8 Likes

How is this something that’s special about numbers? This is true of any zero dimensional type.

Emphasis on Stefan saying ‘like’. I can’t read his mind, but I assumed he just mentioned array because it’s the most encompassing example of an example fulfilling the multidimensional iteration protocol, not because he believes you have to be an array to implement the protocol.

Types are allowed to partake in the interation protocol and define things like dimensionality despite not being arrays. In addition to Number, both Char and Ref are zero dimensional, indexable containers. Char is immutable and Ref is mutable.

julia> 'a' isa AbstractArray, Ref("hi") isa AbstractArray
false, false

julia> ndims('a'),  ndims(Ref("hi"))
(0, 0)

julia> 'a'[],  Ref("hi")[]
('a', "hi")


julia> size(Ref("hi")), size('a')
((), ())
2 Likes

I don’t know what a “zero dimensional type” is. Does it simply mean size(::T) = ()? In which case yes, if T implements the AbstractArray interface, it is of course implied that it is a collection of itself. Numbers are an odd duck since they don’t claim to implement the AbstractArray interface, but iterate as if they did. Which is of course fine, it just doesn’t help with the rationalization of thinking that they are 0-dimensional arrays, when they aren’t really.

Just to clarify, I mean that numbers are special in that eg if x::T isa Number, it behaves like an iterator with size (). This does not hold in general for most types (that are not some kind of collection). Note that the issue is well-known and generally accepted as valid (even if the decision was not to fix). See eg

Suggestions for other types come up from time to time, eg

Whenever these are rejected (which, again, I agree with), it implicitly means that numbers get special treatment.

I am aware of this :wink: The issue is why numbers do this, and why not other types we think of as atoms. (The other basic type that does this is Char. AFAIK Ref was meant to be something like a container).

2 Likes

It’s not an array interface though. It’s a language wide, systematic way of talking about the dimensional of objects. Having such an interface is vital to things like broadcasting working consistently throughout the language.

Things either have a notion of dimensionality and indexing or they don’t. We know that numbers are scalars, so it makes sense to make them satisfy the interface of zero dimensional objects. If I define some random type struct Foo end, Julia can’t know a-priori what sort of object I intend it to be so it doesn’t try to guess.

All other zero dimensional objects in the language that I know of implement this interface, so I’d say making Number opt out of it would be ‘special casing’, not opting in.

This is the key point here. We don’t “know” that numbers are 0-dimensional objects. That’s a convention that some fields use, and it has its advantages. But for a programming language, this is a design choice like any other, with various trade-offs.

Precisely — this means that an API which accepts a number and relies on it being iterable is hard to extend to other kinds of objects which don’t intend to be iterable collections of themselves.

1 Like