Using multiple dispatch with parametric constructors produces ambiguous call

This code

const CompoundTuple{T} = Tuple{Compound{T}, T}

struct ChemEquation{T<:Number}
    tuples::Vector{CompoundTuple{T}}
end
ChemEquation(tuples::Vector{CompoundTuple{T}}) where T = ChemEquation{T}(tuples)

ChemEquation{T}(str::AbstractString) where T = ChemEquation(compoundtuples(str, T))
ChemEquation(str::AbstractString) = ChemEquation{Int}(str)

produces this error

LoadError: LoadError: MethodError: ChemEquation{Int64}(::String) is ambiguous. Candidates:
    (::Type{ChemEquation{T}})(tuples) where T<:Number in ChemEquations at C:\Users\mirav\Documents\dev\julia\ChemEquations.jl\src\chemequation.jl:22
    (::Type{ChemEquation{T}})(str::AbstractString) where T in ChemEquations at C:\Users\mirav\Documents\dev\julia\ChemEquations.jl\src\chemequation.jl:45
  Possible fix, define
    (::Type{ChemEquation{T}})(::AbstractString) where T<:Number

with stacktrace pointing to the last line of my code.

Note: removing ChemEquation(tuples::Vector{CompoundTuple{T}}) where T = ChemEquation{T}(tuples) doesn’t change anything.

The proposed fix actually works, but I don’t understand why, since ChemEquation.tuples is definitely typed. What confuses the multiple dispatch system in this case, and why does the proposed fix work?

If you want to make the life easier for people who want to help you, you’re better off providing a complete code to play with. You don’t define Compound{T} and even if I add the line

struct Compound{T} end

I don’t get any error with your code because you just define functions. I assume you get an error when calling one of the constructors. Which one? How do you call it?

2 Likes

Changing Compound in CompoundTuple{T} = Tuple{Compound{T}, T} to Complex for example still produces the same error, so I don’t think Compound is the issue. I thought adding that code would bloat the question.

As I noted, the error appears when calling ChemEquation{Int}(str), also unrelated to Compound.

Compound is just another struct defined in similar manner, but it has two fields, so errors like this one don’t appear.

const ElementTuple{T} = Tuple{String, T}

struct Compound{T<:Number}
    tuples::Vector{ElementTuple{T}}
    charge::T
end
Compound(tuples::Vector{ElementTuple{T}}, charge::T) where T = Compound{T}(tuples, charge)

Compound{T}(str::AbstractString) where T = Compound(elementtuples(str, T), charge(str, T))
Compound(str::AbstractString) = Compound{Int}(str)

Well, it’s pretty much the opposite: if you want help with code, you should very likely provide the code. As little as possible, but working code. See PSA: make it easier to help you and https://stackoverflow.com/help/minimal-reproducible-example. Your code doesn’t define elementtuples nor charge.

I guess you’re now talking about Compound{Int}(str) (what is str? I assume it’s any string)?

If you want to be helped, providing incomplete code with wrong references is not going to put people in the right mood.

1 Like

The source code is in https://github.com/zdroid/ChemEquations.jl, but it has hundreds of lines. I don’t think it contains any useful information, since I identified the error to be something else.

The problem here is that

struct ChemEquation{T<:Number}
    tuples::Vector{CompoundTuple{T}}
end

produces a constructor without definitely typed tuples. One fix for that is this:

struct ChemEquation{T<:Number}
    tuples::Vector{CompoundTuple{T}}
    ChemEquation{T}(tuples::Vector{CompoundTuple{T}}) where T = new{T}(tuples)
end

But the explanation in Parametric constructors docs is unclear, so I’m asking here for that. Sorry if I wasn’t clear enough.

Please post a complete question with complete, minimal working code and example usage which demonstrates current input and output, and desired output – in other words, follow the advice that Mosè already posted by following the PSA document.

If you do that you are likely to get good and useful help. (And if you don’t, you aren’t.)

1 Like

For this one, T <: Number matches the Int parameter in your call; the argument matches too (since it’s Any here).

For this one, the type of the argument matches the string type in the argument of your call and the parameter T matches too (since it’s unconstrained here).

So both match, and one matches narrowly with the type parameter and the other matches narrowly with the argument type; neither method is more specific than the other. So it’s ambiguous.

2 Likes

In which way the previous post doesn’t satisfy those conditions? Nevertheless, I’ve made a simplified example which produces the same error:

julia> struct Hey{T<:Number}
           a::Vector{T}
       end

julia> Hey{T}(str::AbstractString) where T = Hey{T}(T.(collect(str)))

julia> Hey(str::AbstractString) = Hey{Int}(str)
Hey

julia> Hey("123")
ERROR: MethodError: Hey{Int64}(::String) is ambiguous. Candidates:
  (::Type{Hey{T}})(a) where T<:Number in Main at REPL[1]:2
  (::Type{Hey{T}})(str::AbstractString) where T in Main at REPL[2]:1
Possible fix, define
  (::Type{Hey{T}})(::AbstractString) where T<:Number

I understood that, but I’m asking is this desired behavior? It seems illogical, for a definitely typed struct field to accept any type, instead of any type under parametric conditions (i.e. T<:Number).

Ah, I hadn’t realized that was your question. I’m not sure; I tried

Hey{T}(str::AbstractString) where T = Hey{Int}(str)

to see if you could use this behavior to do a fallback method or something like that, but it errors (ERROR: TypeError: in Hey, in T, expected T<:Number, got Type{String}).