A bug in tuple type-inferencing, or a subtlety?


#1

In the code below, test_inf2 does not work (see the error message that follows), and yet test_inf1 and test_inf3 are fine. Is this a bug in the type-inference system? Note that for my actual code, the solution presented by test_inf3 is suitable, but the manual specifically states that users should prefer the technique of test_inf2 over that of test_inf3.

module test_valtype

function test_inf1()
    test_inf1_(Val{3})
end

function test_inf1_(::Type{Val{N}}) where N
    println("N = ", N)
    nothing
end

function test_inf2()
    test_inf2_((2, Val{3}))
end

function test_inf2_(::Tuple{Int, Type{Val{N}}}) where N
    println("N = ", N)
    nothing
end

function test_inf3()
    test_inf3_((2, Val{3}()))
end

function test_inf3_(::Tuple{Int, Val{N}}) where N
    println("N = ", N)
    nothing
end

end

Here is the error:

julia> test_valtype.test_inf1()
N = 3

julia> test_valtype.test_inf2()
ERROR: MethodError: no method matching test_inf2_(::Tuple{Int64,DataType})
Closest candidates are:
  test_inf2_(::Tuple{Int64,Type{Val{N}}}) where N at C:\Users\vavasis\ownCloud\Documents\Katerina\cohesive\conic_jl\test_valtype.jl:17
Stacktrace:
 [1] test_inf2() at C:\Users\vavasis\ownCloud\Documents\Katerina\cohesive\conic_jl\test_valtype.jl:13

julia> test_valtype.test_inf3()
N = 3

#2

FWIW, as someone who benchmarked performance-sensitive tuples a lot, I would recommend the opposite. But I’d love to know what the manual’s justification is. For one, Val{N} is isbits but Type{Val{N}} isn’t.


#3

The recommendation was changed to use Val{N} instead of Type{Val{N}

For consistency across Julia, the call site should always pass a Val instance rather than using a type, i.e., use foo(Val(:bar)) rather than foo(Val{:bar}).


#4

Thanks for the prompt responses and clarifications! One last question: the reason that my code is structured as in the above snippet is that I want a performant “function barrier” between test_inf1 and test_inf1_. In particular, the number 3 that appears in test_inf1 could be an arbitrary Int program variable, but when it reaches test_inf1_, it is now a compile-time constant that can lead to loop-unrolling or other code optimizations. The same holds for test_inf3. Will it work the way that I imagine? Is this a standard “Julian” idiom?


#5

I believe that ntuple will always unroll, but every other construct is governed by the compiler’s optimization heuristics. I wrote Unrolled.jl to get more control over the process.