Resolving method ambiguity with Union

Suppose I am trying to define a hvcat for a user type, and after I deal with the first argument (which can be an Int, or a Tuple{Vararg{Int}), the methods share a lot of code. So it would be ideal for me to just branch on the first argument and then implement one method.

But ambiguities do not allow that. MWE (for just this part):

julia> struct Foo end

julia> Base.hvcat(blocks_per_row::Union{Tuple{Vararg{Int}}, Int}, foos::Foo...) = Foo()

julia> [Foo() Foo();
       Foo() Foo()]
ERROR: MethodError: hvcat(::Tuple{Int64, Int64}, ::Foo, ::Foo, ::Foo, ::Foo) is ambiguous.

Candidates:
  hvcat(rows::Tuple{Vararg{Int64}}, xs...)
    @ Base abstractarray.jl:2155
  hvcat(blocks_per_row::Union{Int64, Tuple{Vararg{Int64}}}, foos::Foo...)
    @ Main REPL[37]:1

Now I could extract the core logic of hvcat which does the actual heavy lifting (elided above) to an auxiliary function and have the two methods call that, but I am wondering if there is an easier way.

as an aside:

  1. hvcat(::Int, ...) has a generic fallback so strictly speaking I do not need to define both methods, but this undocumented so I am not sure if this is something I can rely on).
  2. which form actually lowers to hvcat(::Int, ...)? Everything I tried lowers to the other method with the tuple.

Not really sure this is what you’d want, but annotating the type of matrix seems to be a workaround for the ambiguity issue.

julia> Foo[Foo() Foo();Foo() Foo()]
2x2 Matrix{Foo}
Foo()  Foo()
Foo()  Foo()

Sure, because that calls a different method (typed_hvcat).

1 Like

I don’t think anything does. The minimal matrix syntax lowering to hvcat takes a 1-Tuple:

julia> Meta.@lower [1;] # nope
:($(Expr(:thunk, CodeInfo(
    @ none within `top-level scope`
1 ─ %1 = Base.vcat(1)
└──      return %1
))))

julia> Meta.@lower [1 2] # nope
:($(Expr(:thunk, CodeInfo(
    @ none within `top-level scope`
1 ─ %1 = Base.hcat(1, 2)
└──      return %1
))))

julia> Meta.@lower [1 2;] # bingo
:($(Expr(:thunk, CodeInfo(
    @ none within `top-level scope`
1 ─ %1 = Core.tuple(2)
│   %2 = Base.hvcat(%1, 1, 2)
└──      return %2
))))

I think it’s weird that hvcat takes a tuple of integers for each row in the first place when all the integers must be equal. This would be a lot simpler to extend if hvcat instead took a Matrix shape Tuple{Int, Int} the lowerer figures out.

No they don’t, try

A = zeros(1, 2)
Meta.@lower [A;
             1 1]

which is perfectly fine.

1 Like

Thank you for the correction. I should’ve checked that before, that was dumb of me.

I’ve been experimenting and searching for solutions, and it seems there’s no way of resolving the ambiguity issue when using Unions.
Did find a somewhat better solution, using @eval to generate methods for each types.

for T in (:(Tuple{Vararg{Int}}), :Int)
    @eval Base.hvcat(blocks_per_row::$T, foos::Foo...)=Foo()
end

I guess this is the next best thing you can try when Unions can’t do the job.

This is what I’d do, I don’t see anything wrong with it.

1 Like

Since then I discovered that there is a fallback in Base method that handles the ::Int case, and in any case, nothing lowers to that. So it is enough to write the ::Tuple{Vararg{Int}} method.

1 Like