I find this a bit weird:
julia> x = (1, "a fish")
(1, "a fish")
julia> x isa NTuple{2}
false
julia> NTuple{2}(x) ≡ x
true
That is, T(x) ≡ x yet x is not a T.
I find this a bit weird:
julia> x = (1, "a fish")
(1, "a fish")
julia> x isa NTuple{2}
false
julia> NTuple{2}(x) ≡ x
true
That is, T(x) ≡ x yet x is not a T.
Expanding out the NTuple alias:
julia> x isa Tuple{Any, Any}
true
julia> Tuple{Any,Any} <: Tuple{T,T} where T
false
And the underlying conversion dispatches to convert(::Type{T}, x::NTuple{N,Any}) where {N, T<:Tuple}, not sure how much sense any of this makes.
Just to clarify: I understand why this happens. Each step is rational and makes sense given how Julia works, it’s the bottom line that bothers me.
I have no good suggestion on how to fix this. We could make NTuple{N}(x) error unless elements of x have the same concrete type, but I that would break a lot of existing code.
If this is a bona fide type constructor bug, they could sweep General for NTuple{N} calls and check whether a fix is worth it. Wouldn’t be the first time Julia made a rare “breaking” change.
Kinda unnecessary to say, but I never really liked that tuples are covariant unlike everything else, and I think this is an occasional source of confusion that results in things like this. Wouldn’t things work just as well if tuples were invariant and themselves only allowed concrete type parameters? Maybe there’s a good reason for the notation beyond saving typing <:.
I wonder if it would be breaking to instead find a way to migrate NTuple{2} to mean Tuple{Any, Any}, rather than Tuple{T, T} where {T <: Any}.
This might require some nasty special casing, but maybe there’s a nice way to do it.
This is something that a lot of people have been wishing for in various forms for a long time.
Essentially the general invariant people want is that for a type T, then T(args...) should always produce a T. The general case of this would be massively breaking, but I think is something that at the very least should be set aside as a 2.0 milestone issue.
I don’t think that the omitted NTuple parameter being Any would be an intuitive exception, on top of tuple types being unusual already.
There’s more options here than changing what it means to omit a parameter. NTuple is a very special object that expands with special cased semantics that users can’t replicate themselves in another type.
I’m speculating that we could maybe tweak how it expands out into a Tuple without wreaking too much havok. Currently when you write NTuple{3}, this first turns into NTuple{3, T} where {T} and then into Tuple{T, T, T} where {T}, but it’s not actually obvious to me that this should be the case.
We could also make this mean NTuple{T where {T}, U, where {U}, V where {V}} by flipping the order at which we expand that special semantic. I suspect we don’t want to do this for a number of reasons, but it’s maybe worth pondering
(but probably not, I’m just spitballing, which maybe I shouldnt do)
I don’t think it is, it is just a type alias.
Nothing is special cased about NTuple per se.