# Type stability when looping over fields in a heterogenous data struct

**URL:** <https://discourse.julialang.org/t/type-stability-when-looping-over-fields-in-a-heterogenous-data-struct/101643>\
**Category:** New to Julia\
**Tags:** data\_structures, type-stability\
**Created:** [July 15, 2023, 2:17am UTC](https://discourse.julialang.org/t/type-stability-when-looping-over-fields-in-a-heterogenous-data-struct/101643 "2023-07-15T02:17:45Z")\
**Posts on this page:** 9\
**Page:** 1

<div class="post-metadata">

**Author:** ![james3](https://avatars.discourse-cdn.com/v4/letter/j/b5e925/32.png) [@james3](https://discourse.julialang.org/u/james3)\
**Post date:** [July 15, 2023, 2:17am UTC](https://discourse.julialang.org/t/type-stability-when-looping-over-fields-in-a-heterogenous-data-struct/101643/1 "2023-07-15T02:17:45Z")

</div>

I have a number of structs that contain arrays that vary in their dimension. I have a function that takes takes these structs, and loops over the fields, performing some operation. Suppose, for simplicity, that I was just calculating the total length of all these arrays. I can accomplish this using the following

```julia
function test(xyz)
    total_length = 0
    fields = map(f -> getproperty(xyz, f), propertynames(xyz))
    @inbounds for field in fields
        total_length += length(field)
    end
    return total_length
end

```

If I have a struct such as

```julia
struct y_t
    a::Array{Float64, 1}
    b::Array{Float64, 2}
    c::Array{Float64, 3}
end

```

with, for example, `y = y_t(zeros(3), zeros(10, 2), zeros(2,5,10))`, then the function `test` called with type `y_t` is not type stable as field is either a vector, matrix, or multi-dimensional array. How can I achieve type stability and avoid any allocations? I wish to maintain heterogeneous data in my structs, if possible. Thanks.

---

<div class="post-metadata">

**Author:** ![Benny](https://avatars.discourse-cdn.com/v4/letter/b/49beb7/32.png) [@Benny](https://discourse.julialang.org/u/Benny)\
**Post date:** [July 15, 2023, 4:22am UTC](https://discourse.julialang.org/t/type-stability-when-looping-over-fields-in-a-heterogenous-data-struct/101643/2 "2023-07-15T04:22:33Z")

</div>

In this case, the compiler was able to determine that `field` only took on the 3 types in the tuple resulting from the `map`, so the returned `total_length` was inferred as `Int64`. I wouldn’t worry about the method, it doesn’t even allocate.

But if you really don’t like that `@code_llvm`-red type-unstable variable iterating through a heterogenous tuple, you can do tuple to tuple computations, like `test2(xyz) = sum(ntuple(i -> length(getfield(xyz, i)), fieldcount(typeof(xyz))))`. That’s for simpler cases, generally I see inlined recursive tuple constructions, like `map(f, t::Tuple) = (@inline; (f(t[1]), map(f,tail(t))...))`.

---

<div class="post-metadata">

**Author:** ![jw3126](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/jw3126/32/3086_2.png) [@jw3126](https://discourse.julialang.org/u/jw3126)\
**Post date:** [July 15, 2023, 6:46am UTC](https://discourse.julialang.org/t/type-stability-when-looping-over-fields-in-a-heterogenous-data-struct/101643/3 "2023-07-15T06:46:53Z")

</div>

Another way to get clean code warntype is to use ConstructionBase:

```julia
using ConstructionBase
function test2(xyz)
    sum(length, getproperties(xyz))
end

@code_warntype test2(xyz)

```

```julia
# MethodInstance for test2(::y_t)
# from test2(xyz) in Main at /home/jan/delme/doit.jl:9
# Arguments
# #self#::Core.Const(test2)
# xyz::y_t
# Body::Int64
# 1 ─ %1 = Main.getproperties(xyz)::NamedTuple{(:a, :b, :c), Tuple{Vector{Float64}, Matrix{Float64}, A
# rray{Float64, 3}}}
# │ %2 = Main.sum(Main.length, %1)::Int64
# └── return %2

```

You can use `getproperties` and `setproperties` to convert between “struct” land and NamedTuple land and typically `Base` has good implementations of doing all kinds of manipulations with NamedTuples.

---

<div class="post-metadata">

**Author:** ![nsajko](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/nsajko/32/221187_2.png) [@nsajko](https://discourse.julialang.org/u/nsajko)\
**Post date:** [July 15, 2023, 4:06pm UTC](https://discourse.julialang.org/t/type-stability-when-looping-over-fields-in-a-heterogenous-data-struct/101643/4 "2023-07-15T16:06:08Z")

</div>

> [@james3](#):
>
> If I have a struct such as
> 
> ```julia
> struct y_t
> a::Array{Float64, 1}
> b::Array{Float64, 2}
> c::Array{Float64, 3}
> end
> 
> ```

Why not eliminate your problem with something like this:

```julia
struct y_t{n, T <: NTuple{n, AbstractArray}}
  arrays::T
end

```

---

<div class="post-metadata">

**Author:** ![nsajko](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/nsajko/32/221187_2.png) [@nsajko](https://discourse.julialang.org/u/nsajko)\
**Post date:** [July 15, 2023, 4:08pm UTC](https://discourse.julialang.org/t/type-stability-when-looping-over-fields-in-a-heterogenous-data-struct/101643/5 "2023-07-15T16:08:46Z")

</div>

> [@Benny](#):
>
> ntuple(i → length(getfield(xyz, i)), fieldcount(typeof(xyz)))

`Val(fieldcount(typeof(xyz)))` could be better than `fieldcount(typeof(xyz))`, if the field count is greater than ten, at least.

---

<div class="post-metadata">

**Author:** ![Henrique\_Becker](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/henrique_becker/32/15443_2.png) [@Henrique\_Becker](https://discourse.julialang.org/u/Henrique_Becker)\
**Post date:** [July 15, 2023, 7:40pm UTC](https://discourse.julialang.org/t/type-stability-when-looping-over-fields-in-a-heterogenous-data-struct/101643/6 "2023-07-15T19:40:40Z")

</div>

This structure will be far less performant, no? Because it has `AbstractArrays`s inside.

---

<div class="post-metadata">

**Author:** ![Benny](https://avatars.discourse-cdn.com/v4/letter/b/49beb7/32.png) [@Benny](https://discourse.julialang.org/u/Benny)\
**Post date:** [July 15, 2023, 10:31pm UTC](https://discourse.julialang.org/t/type-stability-when-looping-over-fields-in-a-heterogenous-data-struct/101643/7 "2023-07-15T22:31:20Z")

</div>

`NTuple{3, AbstractArray}` is an abstract type like `Tuple{T,U,V} where {T<:AbstractArray, U<:AbstractArray, V<:AbstractArray}`, so `T` would just be `typeof(arrays)`.

```julia
julia> y = y_t((zeros(3), zeros(10, 2), zeros(2,5,10))); typeof(y)
y_t{3, Tuple{Vector{Float64}, Matrix{Float64}, Array{Float64, 3}}}

```

---

<div class="post-metadata">

**Author:** ![nsajko](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/nsajko/32/221187_2.png) [@nsajko](https://discourse.julialang.org/u/nsajko)\
**Post date:** [July 15, 2023, 11:51pm UTC](https://discourse.julialang.org/t/type-stability-when-looping-over-fields-in-a-heterogenous-data-struct/101643/8 "2023-07-15T23:51:41Z")

</div>

> [@Henrique\_Becker](#):
>
> This structure will be far less performant, no? Because it has `AbstractArrays`s inside.

In `T <: NTuple{n, AbstractArray}`, the right-hand side is merely a constraint on the left-hand side, saying that `T` must subtype `NTuple{n, AbstractArray}`. Note that, unlike other Julia types, tuples are _covariant_, thus:

```julia-repl
julia> Tuple{Vector{Float64}, Matrix{Float64}} <: NTuple{2, AbstractArray}
true

```

---

<div class="post-metadata">

**Author:** ![Henrique\_Becker](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/henrique_becker/32/15443_2.png) [@Henrique\_Becker](https://discourse.julialang.org/u/Henrique_Becker)\
**Post date:** [July 17, 2023, 10:44pm UTC](https://discourse.julialang.org/t/type-stability-when-looping-over-fields-in-a-heterogenous-data-struct/101643/9 "2023-07-17T22:44:03Z")

</div>

Yes, I forgot about the covariant bit, I am sorry. If it was not this exception, then my comment would have made sense.
