Union as constructor fails on nightly

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?

Edit: It seems the change that breaks my code is this one: fix a transitivity error in method specificity by JeffBezanson · Pull Request #29405 · JuliaLang/julia · GitHub
The code works on 1.1.0-DEV.384 but fails on 1.1.0-DEV.385

Your code does look rather ambiguous to me… did you try replacing U(X::U) = X by

U(x::N{X}) where {X} = x
U(x::I{X}) where {X} = x

This works for me in 1.0. (I haven’t downloaded any recent nightly).

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:

f(X::Number) = N{X}()
f(X::Integer) = I{X}()
f(X::U) = X
f(f(1)) # returns I{1}()

(The actual code that I’m trying to get to work is here: GitHub - perrutquist/StaticNumbers.jl: Static numbers in Julia )

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.

1 Like

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?