Infer type from tuple argument

Hi all,
I have been having problems making my functions type stable as I don’t entirely understand how it works.
I managed to make most of my functions type stable (I think) but I am having problems with the most performance sensitive one. I believe the problem has to do with the fact that I decided to use multiple dispatch and one of the outputs is an array with different size depending on what type it is fed with.
I tried to build a minimal working example of my problem. In my code I am doing more complicated things in the iterator function, calling all sorts of other user-defined functions, but I do have a max operator in there and I believe that’s where the problems start.
Here’s the MWE:

abstract type Agent end
abstract type Entr <: Agent end
abstract type Work <: Agent end
immutable Ye <: Entr end
immutable Yw <: Work end
immutable Oe <: Entr end
immutable Ow <: Work end

mutable struct Dimensions
    x_dim::Int64
    y_dim::Int64
    z_dim::Int64
end

mutable struct Household  
    h_type::Agent
    dim::Dimensions

    V::Array{Float64,N} where {N}
    pol_tuple::NTuple{N,Array{Float64}} where {N}
end

function Dimensions()
    x_dim = 10; y_dim = 2; z_dim = 2
    return Dimensions(x_dim,y_dim,z_dim)
end
D = Dimensions()

function Household( h_type::Agent;
                    dim::Dimensions = D)
    
    function initiate(h_type::Ye)
        V = zeros(Float64,dim.x_dim,dim.y_dim,dim.z_dim)
        a = zeros(Float64,dim.x_dim,dim.y_dim,dim.z_dim)
        c = zeros(Float64,dim.x_dim,dim.y_dim,dim.z_dim)

        return V::Array{Float64,3}, (a,c)
    end

    function initiate(h_type::Yw)
        V = ones(Float64,dim.x_dim,dim.y_dim)
        k = ones(Float64,dim.x_dim,dim.y_dim)
        c = ones(Float64,dim.x_dim,dim.y_dim)

        return V::Array{Float64,2}, (k,c)
    end

    (V, pol_tuple) = initiate(h_type)

    return Household(h_type,dim,V,pol_tuple)
end

function iterator!(hs::NTuple{N,Household} where {N})

    (hye,hyw) = hs
    Vye = similar(hye.V::Array{Float64,3})
    Vyw = similar(hyw.V::Array{Float64,2})
    
    Vye = max.(2.0*rand(hye.dim.x_dim,hye.dim.y_dim,hye.dim.y_dim)-1.0,hye.V::Array{Float64,3})
    Vyw = max.(2.0*rand(hye.dim.x_dim,hye.dim.y_dim),hyw.V::Array{Float64,2})

    hye.V::Array{Float64,3} = Vye::Array{Float64,3}
    hyw.V::Array{Float64,2} = Vyw::Array{Float64,2}
end

hye = Household(Ye())
hyw = Household(Yw())
iterator!((hye,hyw))

Looking at the output of @code_warntype I see many issues, the biggest of which seems to be that, when it tries to get the :V field of either hyw or hye, it does not know what’s its size.
I suspect the problem here is that both are the outcome of the initiate function, which outputs differently sized arrays depending on whether the type is ::Yw or ::Ye.

Also, if you have any sorts of general performance suggestions they are very well appreciated!
Thanks a lot in advance.

This is a really big example, can you please try to find a minimal example that reproduces your problem?

In the meanwhile, there’s one issue I can see in your code:

mutable struct Household  
    h_type::Agent
    dim::Dimensions

    V::Array{Float64,N} where {N}
    pol_tuple::NTuple{N,Array{Float64}} where {N}
end

You have two (edit: three, see reply below) non-concretely typed fields including V and pol_tuple. This means that every Household object must be able to hold any number of dimensions in V. That’s probably not what you want, and it’s definitely going to cause performance issues. See: https://docs.julialang.org/en/latest/manual/performance-tips/#Avoid-fields-with-abstract-type-1

The common solution to this kind of problem is to make a parameterized type:

struct Household{N}
  V::Array{Float64, N}
end

or, even more flexibly:

struct Household{T <: Real, N}
  V::Array{T, N}
end

Try

mutable struct Household{A <: Agent, N}   
    h_type::A
    dim::Dimensions

    V::Array{Float64,N}
    pol_tuple::NTuple{N,Array{Float64}}
end

Agent is also abstract.

Ah, good point, thanks.

Ah, ok! Got it. It makes sense now.

Thanks a lot!