How to resolve `Any`s in this function?

Consider this:

using ComponentArrays, Parameters

function test(g, a; data=data)
    @unpack P = data

    if g == 1
        oc = (g, a) -> P[g, a]
    elseif g == 2
        oc = (g, a) -> P[g, a] + 1
    end
    return oc(g, a)
end

const testca = ComponentArray{Float64}(P = [1 2; 3 4])

How can I get rid of those Anys in the following?

@code_warntype test(1, 2; data = testca)
Variables
  #unused#::Core.Compiler.Const(var"#test##kw"(), false)
  @_2::NamedTuple{(:data,),Tuple{ComponentVector{Float64}}}
  @_3::Core.Compiler.Const(test, false)
  g::Int64
  a::Int64
  data::ComponentVector{Float64}
  @_7::ComponentVector{Float64}

Body::Any
1 ─ %1  = Base.haskey(@_2, :data)::Core.Compiler.Const(true, false)
β”‚         %1
β”‚         (@_7 = Base.getindex(@_2, :data))
└──       goto #3
2 ─       Core.Compiler.Const(:(@_7 = Main.data), false)
3 β”„ %6  = @_7::ComponentVector{Float64}
β”‚         (data = %6)
β”‚   %8  = (:data,)::Core.Compiler.Const((:data,), false)
β”‚   %9  = Core.apply_type(Core.NamedTuple, %8)::Core.Compiler.Const(NamedTuple{(:data,),T} where T<:Tuple, false)
β”‚   %10 = Base.structdiff(@_2, %9)::Core.Compiler.Const(NamedTuple(), false)
β”‚   %11 = Base.pairs(%10)::Core.Compiler.Const(Base.Iterators.Pairs{Union{},Union{},Tuple{},NamedTuple{(),Tuple{}}}(), false)
β”‚   %12 = Base.isempty(%11)::Core.Compiler.Const(true, false)
β”‚         %12
└──       goto #5
4 ─       Core.Compiler.Const(:(Base.kwerr(@_2, @_3, g, a)), false)
5 β”„ %16 = Main.:(var"#test#5")(data, @_3, g, a)::Any
└──       return %16

Just as a note, on the upcoming 1.6 (you can try the RC from the julia download page) this infers correctly:

julia> @code_warntype test(1, 2; data = testca)
Variables
  #unused#::Core.Const(var"#test##kw"())
  @_2::NamedTuple{(:data,), Tuple{ComponentVector{Float64}}}
  @_3::Core.Const(test)
  g::Int64
  a::Int64
  data::ComponentVector{Float64}
  @_7::ComponentVector{Float64}

Body::Float64
2 Likes

any word on when it will be released?

I don’t know how this example generalizes but maybe you could try something like a manual return type declaration:

function test(g, a; data=data)::eltype(data)

That works. I’m wondering if eltype(data) is evaluated at the time I define the function, or it acts as a regular argument to test. That is, for every possible type of data, that annotation yields the correct eltype?

To be honest I was surprised that it was valid at all and it doesn’t seem to generalize greatly when I test it, so I have to pass on that question.

While we wait for 1.6, this works and is at least general for the type of element of data:

julia> function test(g, a; data::ComponentArray{T}=data) where T
           @unpack P = data

           if g == 1
               oc = (g, a) -> P[g, a]
           elseif g == 2
               oc = (g, a) -> P[g, a] + 1
           end
           return oc(g, a)::T
       end


That is somewhat similar to what was proposed above, except that the function could return something different from eltype(data) if some other computation is performed afterwards with the result of oc(g,a).

1 Like

How can I annotate oc inside the if ... end? or more generally, how can I annotate the return value of an anonymous function?
The reason I am asking is that in my real example I have to do some arithmetic on oc before returning.

I am not sure if I understand you here, because that would literally mean peform arithmetic on a function. But there you can do, for example:

oc_result = oc(g,a)::T

# do whatever you want with oc_result

return ...

Also maybe you should consider if you really need an anonymous function there, or a simple function that returns what you want:

julia> function oc(g,a,P)
         if g == 1
           return P[g,a]
         else
           return (P[g,a] + 1)
         end
       end

       function test(g, a; data=data)
           @unpack P = data
           oc_result = oc(g,a,P)
           return oc_result
       end

If that is possible, it is also simpler and safer, no need to annotate any type anywhere.

2 Likes

In my application, if I separate the functions like that I get a return type Union{Nothing, Float64} (in Julia 1.6 rc1). I got around it by slightly modifying oc:

function oc(g,a,P)
    if g == 1
        ret = P[g,a]
    else if g == 2
        ret = P[g,a] + 1
    end
    return ret
end

The reason you get Union for this:

function oc(g,a,P)
    if g == 1
        ret = P[g,a]
    elseif g == 2
        ret = P[g,a] + 1
    end
    return ret
end

is quite simple: what should ret be, if g is neither 1 nor 2? The compiler doesn’t pull a value out of thin air, and since no value was specified, that case is inferred as Nothing. Together with the other branches, the complete return type becomes Union{Nothing, Int}, to signal that the returned value is either an Int or nothing.

2 Likes

I get Union for @lmiq ’s proposal. I get Float64 for my modification with ret.

Actually no, I removed the g==2 from the else branch because of that. But you would in the original form of the function.

1 Like

My bad, I misread your example. Do you think there is any penalty for my case? I am also realising that I didn’t exactly copy what I had in my code (the elseif). Now fixed.

Right - if you use a plain else instead of elseif g == 2, the else branch catches all other possible cases, so the final ret always hits either the if or the else branch and is thus defined.

Still I can get around the other cases (my example with elseif) with ret no? I remember back in Julia 0.4, I used to often use that construct.

The compiler is figuring out there that ret is always an integer and the function is being correctly inferred. But note that that is somewhat unpredictable, for example it will return an error if g is not 1 nor 2:

julia> oc(0,2,[1 2 3 ; 1 2 3])
ERROR: UndefVarError: ret not defined

Probably it is not a good idea to rely on that behavior. If g can only be 1 or 2, it is better to add an error message specific for that,

g != 1 && g != 2 && error("g is neither 1 or 2")

or

@assert g == 2

Otherwise set a default value for g somewhere.