3
votes

v06 I want to write a signature that expect 2 to 3 arguments. The first one is either an Integer or Vector of Integer. The second one is either a Vector of Integer or Matrix of Integer. The third one is either a Vector of Integer or not specified.

I first tried it like this

function foo(
a::Union{Integer, Vector{Integer}},
b::Union{Vector{Integer}, Matrix{Integer}},
c::Union{Void, Vector{Integer}} = nothing)

When I call this like this foo(3, [0o7, 0o5]) I get an error telling me that it is unable to match.

ERROR: LoadError: MethodError: no method matching foo(::Int64, ::Array{UInt8,1})
Closest candidates are:
  foo(::Union{Array{Integer,1}, Integer}, !Matched::Union{Array{Integer,1}, Array{Integer,2}}) at ...
  foo(::Union{Array{Integer,1}, Integer}, !Matched::Union{Array{Integer,1}, Array{Integer,2}}, !Matched::Union{Array{Integer,1}, Void}) at ...

Now I understand why julia is unable to match this Array{UInt8} <: Array{Integer} == false, but this just seems to be not smart of julia.

Then I tried this

foo(a::Union{Z1, Vector{Z1}},
    b::Union{Vector{Z2}, Matrix{Z2}},
    c::Union{Void, Vector{Z3}} = nothing
    ) where {Z1 <: Integer, Z2 <: Integer, Z3 <: Integer}

Now julia does not even tell me what does not match!

ERROR: LoadError: MethodError: no method matching foo(::Int64, ::Array{UInt8,1}, ::Void)
Closest candidates are:
  foo(::Union{Array{Z1<:Integer,1}, Z1<:Integer}, ::Union{Array{Z2<:Integer,1}, Array{Z2<:Integer,2}}, ::Union{Array{Z3<:Integer,1}, Void}) where {Z1<:Integer, Z2<:Integer, Z3<:Integer} at ...
  foo(::Union{Array{Z1<:Integer,1}, Z1<:Integer}, ::Union{Array{Z2<:Integer,1}, Array{Z2<:Integer,2}}) where {Z1<:Integer, Z2<:Integer} at ...
1
When you have very complicated type signatures like this, especially with lots of unions, it may be a sign that you should split it into several separate method definitions. In particular, you probably want at least to avoid the foo(a, b, c=nothing), in favour of foo(a, b, c) and foo(a, b). Also, consider whether there is a connection between the types, for example, is a a Vector only when b is a Matrix?DNF
@DNF I understand your concern. The problem is that if I do that that the user gets useless julia error messages instead of the useful ones that I write. E.g. "No method exists where you have a matrix for b and c is Void", i rather have it like this "If you specify b as a matrix you have to specify c as a vector with these dimensions". Infact this signature is the bullshit-check-signature that calls the real method after having checked everything and casted everything to the right type.user2329125
You're setting yourself up to do a whole lot of explicit input checking, which goes against the idea of multiple dispatch. You're causing yourself a lot of pain just to avoid letting people be exposed to ordinary Julia error messages. Also, I would be surprised to be told that c is Void, if I didn't supply any c at all! Suggestion: Split your function into separate methods with correct signatures. Then make a fallback method (or several) that catches the rest: f(a, b, c) = ... without types, and let that one figure out what's wrong with the inputs and issue errors.DNF
Not certain exactly what you want, but something like: foo(a::Integer, b::Vector{<:Integer}) = ... end foo(a::Vector{<:Integer}, b::Matrix{<:Integer}, c::Vector{<:Integer}) = ... end foo(a::Integer, b...) = error("If a is an Integer, b must be a Vector of Integers.") end foo(a::Vector{<:Integer}, b...) = error("If a is an Vector of Integers, b must be a Matrix of Integers and c must be a Vector of Integers...") end (ends added for readability, it's not actually correct to use them.) I think this will actually make it easier to write good error messages.DNF
I do not see benefit in doing it like that for my current problem. I do not believe that this makes my code more readable.user2329125

1 Answers

7
votes

Yes, Array{UInt8} <: Array{Integer} == false. This is called "parametric invariance." Lots of other questions have discussed this topic.

The other problem you're running into, though, is that when you use a static function parameter — that is, f(…) where T — the T must match something since it's available for use in the body of the function. This causes trouble in Unions where T isn't available in every option. I believe there's an open issue about changing this behavior to allow matching Union elements that don't contain T, which would turn that binding into an undefined variable if you tried to access it.

The workaround right now is to use type vars that aren't static parameters of the function. E.g.,

   foo(a::Union{Integer, Vector{<:Integer}},
       b::Union{Vector{<:Integer}, Matrix{<:Integer}},
       c::Union{Void, Vector{<:Integer}} = nothing) = 1