In a package that I’m writing, I’m using a Union as a constructor (with dispatch to select one of the types from the union). Here’s a minimal working example:
struct N{X} <: Number
end
struct I{X} <: Integer
end
U{X} = Union{N{X}, I{X}}
U(X::Number) = N{X}()
U(X::Integer) = I{X}()
U(X::U) = X
i = U(1)
U(i)
The two last lines return the same thing, namely I{1}(). This is working fine in Julia 1.0 and 1.0.1.
However, in Julia 1.1.0-DEV.447 (source build) or recent nightly, I get this:
ERROR: MethodError: Union{I{X}, N{X}} where X(::I{1}) is ambiguous. Candidates:
(::Type{Union{I{X}, N{X}} where X})(X::Number) in Main at REPL[4]:1
(::Type{Union{I{X}, N{X}} where X})(X::Integer) in Main at REPL[5]:1
(::Type{T})(x::T) where T<:Number in Core at boot.jl:724
Possible fix, define
Union{I{X}, N{X}} where X(::I)
Defining U(X::I) = X (as suggested by the error message) does not help.
Am I doing something wrong here, or is there a problem in the Julia development branch?
Thanks. Yes I had tried that. It does not help. It does not even help if I define U(x::I{1}) = x (which is too specific to be useful.)
I agree that my code “looks rather ambiguous”, but I’m not sure exactly where the ambiguity lies, and why it is only ambiguous when I use the type union as a constructor. The following code runs fine:
In your original post, I{1} is a subtype of both Integer and U, which are mutually incomparable, so the call U(::I{1}) is ambiguous since there is a method U(::U) and U(::Integer) but not U(::I{X}). The usual way to break such an ambiguity is to define another method for the subtype, which is why I suggested defining U(x::I{X}) where {X}. I don’t understand why this didn’t work.
Thanks for the explanation. Yes, this makes perfect sense.
I should have used this as an example:
struct N{X} <: Number
end
struct I{X} <: Integer
end
U{X} = Union{N{X}, I{X}}
U(X::Number) = N{X}()
U(X::Integer) = I{X}()
U(x::N{X}) where {X} = x
U(x::I{X}) where {X} = x
i = U(1)
U(i)
I think the problem is this definition in boot.jl:
(::Type{T})(x::T) where {T<:Number} = x
It seems, however I try, I can’t make a definition that is more specific than that, so I always get an ambiguity error. (Before #29405, Julia wasn’t able to detect the ambiguity.)
For now, my solution is simply to use a constructor with a different name from the type union.
Your last message raises another point that I don’t understand and that isn’t documented in the manual, as far as I can see. If T<:U, and they each have constructors, is there any relationship between a constructor call T(...) and U(...)? Does the multiple dispatch mechanism sometimes cause a T(...) in the code to invoke a U(...) or vice versa (in the same way that f(::T) can invoke f(::U)? How about convert calls, i.e., can a convert(T, ::Any) call be handled by convert(U,::Any)? And what about the special case when T is of the form A{X} while U is A?