Why exactly is promote(1:2,[1,2]) designed to throw an error?

The call promote(1:2,[1,2]) throws an error. As far as I understand, this is because on the one hand promote_type(UnitRange{Int64},Array{Int64}) returns AbstractArray{Int64,N} where N, whereas on the other hand both convert(AbstractArray{Int64},1:2) and convert(AbstractArray{Int64},[1,2]) will not change their input as they already both belong to AbstractArray{Int64}.

I understand that there is basically no reason why promote should do anything, as these types of abstract arrays are already compatible in a certain sense, however, the original promote call then throws an error as the output types are not equal. I wonder a bit why is is.

Lets assume that we declare two (or especially more) structs A and B that are involved in a certain type hierarchy (both below the abstract type U), both of which wrap any kind of abstract array and contain different additional information. Functions on A and B may work differently, but mathematically the output is determined only by the contained arrays. Given specific mathematical properties of each the array one captures in A and B however, one may do so much faster.

The properties capured by B are thought to be weaker than A, such that one might define foo(a::U,b::U) = foo(promote(a,b)...) where we have some foo(a1::A, a2::A).

One could now define promote_rule(::Type{A},::Type{B}) = A as well as convert(::Type{A}, b::B) as something that turns b into the type A (basically just recalculating the properties captured in A). However, this is different behavior compared to the AbstractArray behaviour above as we would expect promote_rule(::Type{A},::Type{B}) = U, and even when one captures the type of the array contained as A{T} and B{S}, then promote_rule(::Type{A{T}},::Type{B{S}}) = U{promote_type(T,S)} will still fail as one runs into the problem explained in the beginning. Is this something promotion should not be used for, and if so, how can one circumvent this problem? After all, there might be many further types thought lower in the hierarchy.

Is it better to manually convert everything into type A explicitly in the beginning of foo(u1::U,u2::U) to then call foo(a1::A, a2::A) and add a function foo(b1::B,b2::B)? This however feels like imitating promotion in a manual, maybe improper way. It there a kind of “weak” promotion concept that does not enforce equal output types? (thereby however going against the original idea of promotion in the first place)

Or is there a fundamentally different way how one should do these things anyway? The example above might not be the most simple one to choose, but please pardon me some confusion about this on my side despite reading the manual.

FWIW, this got fixed in either 1.8 or 1.9:

julia> promote(1:2, [1,2])
([1, 2], [1, 2])
3 Likes

Thank you for pointing out that this will be changed! So what promote should really do is convert anything being an AbstractArray into an actual Array if necessary?

I would expect this idea to follow the general rule that given a type chain

A <: AbstractA
AbstractB <: AbstractA
B <: AbstractB
AbstractC <: AbstractB
C <: AbstractC

then canonically, we expect:
promote_type(C,B) = AbstractB where the type of convert(::Type{AbstractB},::C) is B,
promote_type(C,A) = AbstractA where the type of convert(::Type{AbstractA},::C) is A,
promote_type(B,A) = AbstractA where the type of convert(::Type{AbstractA},::B) is A.

Is this the right way of thinking?

However, this leads me to a second question. In original example described in my first post, I would actually desire to only promote (A,B) instances to (A,A) without changing the underlying type of the array - so to speak only promote the first level type, and not necessarily the eltype. That is, to only promote (A{T},B{S}) instances to (A{T},A{S}). This however seems impossible given the complete type equality required for the return values of promote calls.