Search code examples
juliadefault-valuetype-declaration

Julia: Why does specifying the types of arguments in functions lead to (seemingly) inconsistent behaviour?


I have a function that takes in 7 (keyword) arguments, each with its type specified and the last one having a default value, like so:

function dummy(;truefalse1::S, somevar1::T, somevar2::T, somevar3::T, somevar4::T, 
                     scalarvar::Int64, truefalse2::D = falses(3, 3)) where {
    T <: Union{Array{Float64,2}, SubArray{Float64, 2}},
    S <: AbstractArray{Bool}, D <: AbstractArray{Bool}}

    ### 

end

The truefalse* arguments can either be 2-dimensional boolean arrays (BitArray{2}) or a view of it (e.g. view(somearray, 2:4, 3:5)). The somevar* arguments can either be 2-dimensional arrays of type Float64 or the "view" of such an array.

The above works, but this seemingly equivalent version does not (see below for test input):

function dummy(;truefalse1::S, somevar1::T, somevar2::T, somevar3::T, somevar4::T, 
                     scalarvar::Int64, truefalse2::S = falses(3, 3)) where {
    T <: Union{Array{Float64,2}, SubArray{Float64, 2}},
    S <: AbstractArray{Bool}}

    ### 

end

(In other words, the D type has been removed, using the S type for both occurrences.)

The error message is as follows:

ERROR: MethodError: no method matching #dummy#823(::SubArray{Bool,2,BitArray{2},Tuple{UnitRange{Int64},UnitRange{Int64}},false}, ::SubArray{Float64,2,Array{Float64,2},Tuple{UnitRange{Int64},UnitRange{Int64}},false}, ::SubArray{Float64,2,Array{Float64,2},Tuple{UnitRange{Int64},UnitRange{Int64}},false}, ::SubArray{Float64,2,Array{Float64,2},Tuple{UnitRange{Int64},UnitRange{Int64}},false}, ::SubArray{Float64,2,Array{Float64,2},Tuple{UnitRange{Int64},UnitRange{Int64}},false}, ::Int64, ::BitArray{2}, ::typeof(wheretonext))
Closest candidates are:
  #dummy#823(::S, ::T, ::T, ::T, ::T, ::Int64, ::S, ::typeof(dummy)) where {T<:Union{Array{Float64,2}, SubArray{Float64,2,P,I,L} where L where I where P}, S<:(AbstractArray{Bool,N} where N)} at /SomePath/someDummyCode.jl:238
Stacktrace:
 [1] (::var"#kw##dummy")(::NamedTuple{(:truefalse1, :somevar1, :somevar2, :somevar3, :somevar4, :scalarvar),Tuple{SubArray{Bool,2,BitArray{2},Tuple{UnitRange{Int64},UnitRange{Int64}},false},SubArray{Float64,2,Array{Float64,2},Tuple{UnitRange{Int64},UnitRange{Int64}},false},SubArray{Float64,2,Array{Float64,2},Tuple{UnitRange{Int64},UnitRange{Int64}},false},SubArray{Float64,2,Array{Float64,2},Tuple{UnitRange{Int64},UnitRange{Int64}},false},SubArray{Float64,2,Array{Float64,2},Tuple{UnitRange{Int64},UnitRange{Int64}},false},Int64}}, ::typeof(dummy)) at ./none:0
 [2] top-level scope at none:0

Here is a sample set of test input:

julia> using Random; Random.seed!(1234); 

julia> trf1 = rand(5, 10) .> rand(5, 10); trf2 = rand(5, 10) .> rand(5, 10);

julia> smv1 = rand(5, 10); smv2 = rand(5, 10); smv3 = rand(5, 10); smv4 = rand(5, 10); 

These cases produce the aforementioned error using the second function declaration:

julia> dummy(truefalse1 = view(trf1, 2:4, 3:5), somevar1 = view(smv1, 2:4, 3:5),
              somevar2 = view(smv2, 2:4, 3:5), somevar3 = view(smv3, 2:4, 3:5),
              somevar4 = view(smv4, 2:4, 3:5), scalarvar = 1)
julia> dummy(truefalse1 = view(trf1, 2:4, 3:5), somevar1 = view(smv1, 2:4, 3:5),
              somevar2 = view(smv2, 2:4, 3:5), somevar3 = view(smv3, 2:4, 3:5),
              somevar4 = view(smv4, 2:4, 3:5), scalarvar = 1, truefalse2 = falses(3, 3))

However, still using the second function declaration, this case works fine:

julia> dummy(truefalse1 = view(trf1, 2:4, 3:5), somevar1 = view(smv1, 2:4, 3:5),
              somevar2 = view(smv2, 2:4, 3:5), somevar3 = view(smv3, 2:4, 3:5),
              somevar4 = view(smv4, 2:4, 3:5), scalarvar = 1, truefalse2 = view(trf2, 2:4, 3:5))

(Recall: All the above test cases work fine with the first function declaration.)

I would greatly appreciate any suggestions on what might I have done wrong or what might be the relevant subtleties in the Julia language. This is my second week coding in Julia, so I also welcome any other tips. Thank you!


Solution

  • This is because in the first definition, you let truefalse1 and truefalse2 have different types S and D, both types being subtypes of AbstractArray{Bool}. Whereas in the second definition, truefalse1 and truefalse2 must have the same type S (with the constraint that S be a subtype of AbstractArray{Bool}).


    The documentation for Parametric Methods should explain this in more details, but maybe the the following, more minimal example can help get you a grasp of how things work:

    # a and b can be of different types
    function foo(a::S, b::T) where {
        S<:AbstractArray{Bool},
        T<:AbstractArray{Bool}}
    end
    
    # a and b must have the same type
    function bar(a::S, b::S) where {
        S<:AbstractArray{Bool}}
    end
    
    # test data
    a = rand(Bool, 10); # Array
    b = rand(Bool, 10); # Array
    c = view(b, 1:5);   # SubArray
    

    The following calls all work:

    # OK because:
    # - typeof(a) == Array{Bool,1} <: AbstractArray{Bool}
    # - typeof(b) == Array{Bool,1} <: AbstractArray{Bool}
    # => substitute S for Array{Bool,1} and T for Array{Bool,1}
    julia> foo(a, b)
    
    # OK because:
    #   typeof(a) == typeof(b) == Array{Bool,1} <: AbstractArray{Bool}
    # => substitute S for Array{Bool,1}
    julia> bar(a, b)
    
    # OK because:
    # - a isa Array{Bool,1} <: AbstractArray
    # - c isa SubArray{Bool,...} <: AbstractArray{Bool}
    # => substitute S for Array{Bool,1} and T for SubArray{Bool,...}
    julia> foo(a, c)
    
    # Not OK because typeof(a) != typeof(b)
    # there is no concrete type S such that
    # - a isa S
    # - b isa S
    # - S <: AbstractArray
    # => Method does not match
    julia> bar(a, c)
    ERROR: MethodError: no method matching bar(::Array{Bool,1}, ::SubArray{Bool,1,Array
    {Bool,1},Tuple{UnitRange{Int64}},true})                                           
    Closest candidates are:
      bar(::S, ::S) where S<:(AbstractArray{Bool,N} where N) at REPL[2]:3
    Stacktrace:
     [1] top-level scope at REPL[9]:1