# Type instability with tuples of functions and parameters

**URL:** <https://discourse.julialang.org/t/type-instability-with-tuples-of-functions-and-parameters/81588>\
**Category:** Performance\
**Tags:** question, code\_warntype, type-stability\
**Created:** [May 24, 2022, 2:00pm UTC](https://discourse.julialang.org/t/type-instability-with-tuples-of-functions-and-parameters/81588 "2022-05-24T14:00:04Z")\
**Posts on this page:** 14\
**Page:** 1

<div class="post-metadata">

**Author:** ![DanielVandH](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/danielvandh/32/31134_2.png) [@DanielVandH](https://discourse.julialang.org/u/DanielVandH)\
**Post date:** [May 24, 2022, 2:00pm UTC](https://discourse.julialang.org/t/type-instability-with-tuples-of-functions-and-parameters/81588/1 "2022-05-24T14:00:04Z")

</div>

My general application of interest requires a vector of functions of the form `f(x, y, p)`, for some known parameters `p`. I put the functions in a `Tuple` so that their types are known, and define for example the following struct:

```julia
struct Point{T,N}
    x::T
    y::T
    F::Tuple{Vararg{Function,N}}
    F_params::Tuple{Vararg{Union{Nothing, AbstractVector{T}}, N}}
end 

```

The `F_params` is given the type shown so that a user can provide a `Tuple` with an element for each function, and either provide a vector of parameters or no parameters at all (`nothing`). I need to have functions that take in a `Point` as input, for example:

```julia
function TestFnc(pt::Point{T,N}) where {T,N} 
    res = 0.0
    for (f, p) in zip(pt.F, pt.F_params)
        res = res + f(pt.x, pt.y, p)
    end
    return res
end
F1 = (x, y, p) -> x^2 + y^2 - p[1]
F2 = (x, y, p) -> x*y 
F3 = (x, y, p) -> x + y + p[2] - p[3]*p[1]
p₁ = [1.0]; p₂ = nothing; p₃ = [1.0, 2.0, 3.0]
x = 0.2; y = 0.7 
pt = Point(x, y, (F1, F2, F3), (p₁, p₂, p₃))

```

When I use these definitions, I get type instabilities according to `@code_warntype` (see below). How can I improve my struct so that all types are properly inferred?

```julia
@code_warntype TestFnc(pt)

```

```julia
MethodInstance for TestFnc(::Point{Float64, 3})
  from TestFnc(pt::Point{T, N}) where {T, N} in Main at c:\Users\licer\testjl.jl:7
Static Parameters
  T = Float64
  N = 3
Arguments
  #self#::Core.Const(TestFnc)
  pt::Point{Float64, 3}
Locals
  @_3::Union{Nothing, Tuple{Tuple, Tuple}}
  res::Any
  @_5::Int64
  p::Any
  f::Any
Body::Any
1 ─ (res = 0.0)
│ %2 = Base.getproperty(pt, :F)::Tuple{Function, Function, Function}
│ %3 = Base.getproperty(pt, :F_params)::Tuple{Union{Nothing, AbstractVector{Float64}}, Union{Nothing, AbstractVector{Float64}}, Union{Nothing, AbstractVector{Float64}}}
│ %4 = Main.zip(%2, %3)::Base.Iterators.Zip
│ (@_3 = Base.iterate(%4))
│ %6 = (@_3 === nothing)::Bool
│ %7 = Base.not_int(%6)::Bool
└── goto #4 if not %7
2 ┄ %9 = @_3::Tuple{Tuple, Tuple}
│ %10 = Core.getfield(%9, 1)::Tuple
│ %11 = Base.indexed_iterate(%10, 1)::Core.PartialStruct(Tuple{Any, Int64}, Any[Any, Core.Const(2)])
│ (f = Core.getfield(%11, 1))
│ (@_5 = Core.getfield(%11, 2))
│ %14 = Base.indexed_iterate(%10, 2, @_5::Core.Const(2))::Core.PartialStruct(Tuple{Any, Int64}, Any[Any, Core.Const(3)])
│ (p = Core.getfield(%14, 1))
│ %16 = Core.getfield(%9, 2)::Tuple
│ %17 = res::Any
│ %18 = Base.getproperty(pt, :x)::Float64
│ %19 = Base.getproperty(pt, :y)::Float64
│ %20 = (f)(%18, %19, p)::Any
│ (res = %17 + %20)
│ (@_3 = Base.iterate(%4, %16))
│ %23 = (@_3 === nothing)::Bool
│ %24 = Base.not_int(%23)::Bool
└── goto #4 if not %24
3 ─ goto #2
4 ┄ return res

```

---

<div class="post-metadata">

**Author:** ![jling](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/jling/32/212909_2.png) [@jling](https://discourse.julialang.org/u/jling)\
**Post date:** [May 24, 2022, 2:22pm UTC](https://discourse.julialang.org/t/type-instability-with-tuples-of-functions-and-parameters/81588/2 "2022-05-24T14:22:06Z")

</div>

```julia
julia> isabstracttype(Function)
true

```

---

<div class="post-metadata">

**Author:** ![DanielVandH](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/danielvandh/32/31134_2.png) [@DanielVandH](https://discourse.julialang.org/u/DanielVandH)\
**Post date:** [May 24, 2022, 2:36pm UTC](https://discourse.julialang.org/t/type-instability-with-tuples-of-functions-and-parameters/81588/3 "2022-05-24T14:36:38Z")

</div>

I don’t know what you are suggesting here, sorry.

---

<div class="post-metadata">

**Author:** ![Vasily\_Pisarev](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/vasily_pisarev/32/7929_2.png) [@Vasily\_Pisarev](https://discourse.julialang.org/u/Vasily_Pisarev)\
**Post date:** [May 24, 2022, 3:28pm UTC](https://discourse.julialang.org/t/type-instability-with-tuples-of-functions-and-parameters/81588/4 "2022-05-24T15:28:09Z")

</div>

The comment is that your type has abstractly-typed fields. Maybe this would suit your needs:

```julia
struct Point{T,N,FTup<:NTuple{N,Function},PTup<:NTuple{N,Union{Nothing, AbstractVector{T}}}}
    x::T
    y::T
    F::FTup
    F_params::PTup
end 

```

---

<div class="post-metadata">

**Author:** ![DanielVandH](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/danielvandh/32/31134_2.png) [@DanielVandH](https://discourse.julialang.org/u/DanielVandH)\
**Post date:** [May 24, 2022, 9:34pm UTC](https://discourse.julialang.org/t/type-instability-with-tuples-of-functions-and-parameters/81588/5 "2022-05-24T21:34:01Z")

</div>

> [@Vasily\_Pisarev](#):
>
> ```julia
> struct Point{T,N,FTup<:NTuple{N,Function},PTup<:NTuple{N,Union{Nothing, AbstractVector{T}}}}
> x::T
> y::T
> F::FTup
> F_params::PTup
> end 
> 
> ```

Ah, I didn’t know that having abstract types was a bad thing here. Thank you. When I make this change, i still get some instances of red text with unions in `@code_warntype`. Are there any problems here, or are these unions fine despite the red text? I list below these unions it warns me of, and also the full code/output after.

- `f::Union{var"#11#12", var"#13#14", var"#15#16"}`
- `@_3::Tuple{Tuple{Union{var"#11#12", var"#13#14", var"#15#16"}, Union{Nothing, Vector{Float64}}}, Tuple{Int64, Int64}}`
- `Core.getfield(%9, 1)::Tuple{Union{var"#11#12", var"#13#14", var"#15#16"}, Union{Nothing, Vector{Float64}}}`
- `Base.indexed_iterate(%10, 1)::Tuple{Union{var"#11#12", var"#13#14", var"#15#16"}, Int64}`
- `Base.indexed_iterate(%10, 2, @_5)::Union{Tuple{Nothing, Int64}, Tuple{Vector{Float64}, Int64}}`  
Code:

```julia
struct Point{T,N,FTup<:NTuple{N,Function},PTup<:NTuple{N,Union{Nothing, AbstractVector{T}}}}
    x::T
    y::T
    F::FTup
    F_params::PTup
end 
function TestFnc(pt::Point{T,N,FTup,PTup}) where {T,N,FTup,PTup} 
    res = 0.0
    for (f, p) in zip(pt.F, pt.F_params)
        res = res + f(pt.x, pt.y, p)
    end
    return res
end
F1 = (x, y, p) -> x^2 + y^2 - p[1]
F2 = (x, y, p) -> x*y 
F3 = (x, y, p) -> x + y + p[2] - p[3]*p[1]
p₁ = [1.0]; p₂ = nothing; p₃ = [1.0, 2.0, 3.0]
x = 0.2; y = 0.7 
pt = Point(x, y, (F1, F2, F3), (p₁, p₂, p₃))
@code_warntype TestFnc(pt)

```

```julia
MethodInstance for TestFnc(::Point{Float64, 3, Tuple{var"#11#12", var"#13#14", var"#15#16"}, Tuple{Vector{Float64}, Nothing, Vector{Float64}}})
  from TestFnc(pt::Point{T, N, FTup, PTup} where {FTup<:Tuple{Vararg{Function, N}}, PTup<:Tuple{Vararg{Union{Nothing, AbstractVector{T}}, N}}}) where {T, N} in Main at c:\Users\licer\testjl.jl:7       
Static Parameters
  T = Float64
  N = 3
Arguments
  #self#::Core.Const(TestFnc)
  pt::Point{Float64, 3, Tuple{var"#11#12", var"#13#14", var"#15#16"}, Tuple{Vector{Float64}, Nothing, Vector{Float64}}}
Locals
  @_3::Union{Nothing, Tuple{Tuple{Union{var"#11#12", var"#13#14", var"#15#16"}, Union{Nothing, Vector{Float64}}}, Tuple{Int64, Int64}}}
  res::Float64
  @_5::Int64
  p::Union{Nothing, Vector{Float64}}
  f::Union{var"#11#12", var"#13#14", var"#15#16"}
Body::Float64
1 ─ (res = 0.0)
│ %2 = Base.getproperty(pt, :F)::Core.Const((var"#11#12"(), var"#13#14"(), var"#15#16"()))
│ %3 = Base.getproperty(pt, :F_params)::Tuple{Vector{Float64}, Nothing, Vector{Float64}}
│ %4 = Main.zip(%2, %3)::Base.Iterators.Zip{Tuple{Tuple{var"#11#12", var"#13#14", var"#15#16"}, Tuple{Vector{Float64}, Nothing, Vector{Float64}}}}
│ (@_3 = Base.iterate(%4))
│ %6 = (@_3::Core.PartialStruct(Tuple{Tuple{var"#11#12", Vector{Float64}}, Tuple{Int64, Int64}}, Any[Tuple{var"#11#12", Vector{Float64}}, Core.Const((2, 2))]) === nothing)::Core.Const(false)        
│ %7 = Base.not_int(%6)::Core.Const(true)
└── goto #4 if not %7
2 ┄ %9 = @_3::Tuple{Tuple{Union{var"#11#12", var"#13#14", var"#15#16"}, Union{Nothing, Vector{Float64}}}, Tuple{Int64, Int64}}
│ %10 = Core.getfield(%9, 1)::Tuple{Union{var"#11#12", var"#13#14", var"#15#16"}, Union{Nothing, Vector{Float64}}}
│ %11 = Base.indexed_iterate(%10, 1)::Tuple{Union{var"#11#12", var"#13#14", var"#15#16"}, Int64}
│ (f = Core.getfield(%11, 1))
│ (@_5 = Core.getfield(%11, 2))
│ %14 = Base.indexed_iterate(%10, 2, @_5)::Union{Tuple{Nothing, Int64}, Tuple{Vector{Float64}, Int64}}
│ (p = Core.getfield(%14, 1))
│ %16 = Core.getfield(%9, 2)::Tuple{Int64, Int64}
│ %17 = res::Float64
│ %18 = Base.getproperty(pt, :x)::Float64
│ %19 = Base.getproperty(pt, :y)::Float64
│ %20 = (f)(%18, %19, p)::Float64
│ (res = %17 + %20)
│ (@_3 = Base.iterate(%4, %16))
│ %23 = (@_3 === nothing)::Bool
│ %24 = Base.not_int(%23)::Bool
└── goto #4 if not %24
3 ─ goto #2
4 ┄ return res

```

---

<div class="post-metadata">

**Author:** ![DNF](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/dnf/32/10191_2.png) [@DNF](https://discourse.julialang.org/u/DNF)\
**Post date:** [May 24, 2022, 9:46pm UTC](https://discourse.julialang.org/t/type-instability-with-tuples-of-functions-and-parameters/81588/6 "2022-05-24T21:46:10Z")

</div>

You seem to be emulating a class-based OOP approach when putting functions inside your structs. It’s not exactly clear from your example if this is necessary.

Can you reftame your problem in terms of dispatch with external methods, or must each instance of `Point` have its own individual tuple of functions?

Basically, your code is not idiomatic for Julia, and it’s not clear if that is unavoidable.

---

<div class="post-metadata">

**Author:** ![DanielVandH](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/danielvandh/32/31134_2.png) [@DanielVandH](https://discourse.julialang.org/u/DanielVandH)\
**Post date:** [May 24, 2022, 10:03pm UTC](https://discourse.julialang.org/t/type-instability-with-tuples-of-functions-and-parameters/81588/7 "2022-05-24T22:03:39Z")

</div>

The actual application I’m considering is for some PDE code. These functions in this simple example are to resemble a user providing functions for certain parts of a PDE, and for mixed boundary conditions (for which there can be arbitrarily many, hence my `Vararg` initially). So I think for your second question it’s the latter, i.e. each instance of `Point` does need its own set of functions. So do you think the current state of that code is about as far as I might get, then?

---

<div class="post-metadata">

**Author:** ![DNF](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/dnf/32/10191_2.png) [@DNF](https://discourse.julialang.org/u/DNF)\
**Post date:** [May 24, 2022, 10:13pm UTC](https://discourse.julialang.org/t/type-instability-with-tuples-of-functions-and-parameters/81588/8 "2022-05-24T22:13:29Z")

</div>

No, most likely there is a different approach and better design. Julia is used a lot for PDEs and ODEs, so there’s a lot of expertise.

Unfortunately, I have no experience with this.

But do you anticipate that each point on your grid will have its own unique and individual set of functions? That sounds a bit exotic to me. Is there no grouping, like interior points vs boundary points?

---

<div class="post-metadata">

**Author:** ![DanielVandH](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/danielvandh/32/31134_2.png) [@DanielVandH](https://discourse.julialang.org/u/DanielVandH)\
**Post date:** [May 24, 2022, 10:25pm UTC](https://discourse.julialang.org/t/type-instability-with-tuples-of-functions-and-parameters/81588/9 "2022-05-24T22:25:43Z")

</div>

Sorry, I was a bit unclear (and I probably should have used a different name than `Point`). These functions are primarily for boundary conditions. I’m considering for example a domain \Omega split into N separate segments (e.g. a square with a boundary condition for each side, or a triangle), each with a different boundary condition, and these functions are my `F` in my initial example and their parameters are `F_params`.

---

<div class="post-metadata">

**Author:** ![isaacsas](https://avatars.discourse-cdn.com/v4/letter/i/f6c823/32.png) [@isaacsas](https://discourse.julialang.org/u/isaacsas)\
**Post date:** [May 24, 2022, 11:10pm UTC](https://discourse.julialang.org/t/type-instability-with-tuples-of-functions-and-parameters/81588/10 "2022-05-24T23:10:15Z")

</div>

You can make it type stable / get rid of any unions if `N` is not too big by using a generated function and unrolling the loop (see below). (There is also the `Unrolled.jl` package, but it would require changing your function signature I think.)

If `N` is large, you could wrap your functions in [FunctionWrappers](https://github.com/yuyichao/FunctionWrappers.jl), and store those in the function tuple. However, note this is essentially calling them via a pointer so adds a bit of overhead.

Whatever you decide, you can set `p₂ = Float[]` and drop the `Union` type in the definition of `Point` so the parameter tuple has the same type for all elements.

So something like this (which I’m sure someone else can point out a better way to do) is then type stable:

```julia
 struct Point{T,N,FTup,PTup<:NTuple{N,AbstractVector{T}}}
    x::T
    y::T
    F::FTup
    F_params::PTup
 end

@generated function TestFnc(pt::Point{T,N,FTup,PTup}) where {T,N,FTup,PTup} 
    quote
        res = 0.0
        Base.Cartesian.@nexprs $N i -> res = res + pt.F[i](pt.x, pt.y, pt.F_params[i])
        return res
    end
end

```

---

<div class="post-metadata">

**Author:** ![DanielVandH](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/danielvandh/32/31134_2.png) [@DanielVandH](https://discourse.julialang.org/u/DanielVandH)\
**Post date:** [May 24, 2022, 11:26pm UTC](https://discourse.julialang.org/t/type-instability-with-tuples-of-functions-and-parameters/81588/11 "2022-05-24T23:26:22Z")

</div>

Thank you very much @isaacsas. I’ll have to think about this for quite a while since I’ve never used `@generated`, but this looks very nice. Can `Base.Cartesian.@nexprs` also be used index range is not something like `1:N`? e.g. can it be extended to work for something like

```julia
for j in idx

```

where `idx` could be say `[1, 3, 5, 10, 17]` (just a vector of indices in general)? Looking at the documentation [julia/cartesian.jl at 742b9abb4dd4621b667ec5bb3434b8b3602f96fd · JuliaLang/julia · GitHub](https://github.com/JuliaLang/julia/blob/742b9abb4dd4621b667ec5bb3434b8b3602f96fd/base/cartesian.jl#L111-L126) doesn’t seem to suggest this is a feature.

---

<div class="post-metadata">

**Author:** ![isaacsas](https://avatars.discourse-cdn.com/v4/letter/i/f6c823/32.png) [@isaacsas](https://discourse.julialang.org/u/isaacsas)\
**Post date:** [May 24, 2022, 11:32pm UTC](https://discourse.julialang.org/t/type-instability-with-tuples-of-functions-and-parameters/81588/12 "2022-05-24T23:32:19Z")

</div>

No, `N` would need to be known at compile time via the type I believe. (But I have only used `@generated` a very few times, so am not an expert myself, and I’ve never used `@nexprs`.)

One last option is to use recursion to keep splitting off one element of the tuple. As long as `N` is not gigantic the compiler can make it type stable:

```julia
 struct Point{T,FTup <: Tuple}
    x::T
    y::T
    F::FTup # I'll make it a tuple of tuples to make the recursion easier
end

pt = Point(x, y, ((F1,p₁), (F2,p₂), (F3,p₃)))

function TestFnc(x,y,F,args...)
    F[1](x,y,F[2]) + TestFnc(x,y,args...)
end

function TestFnc(x,y,F)
    F[1](x,y,F[2])
end

function TestFnc(pt::Point) 
    TestFnc(pt.x,pt.y,pt.F...)
end

```

I think this should also be type stable.

---

<div class="post-metadata">

**Author:** ![isaacsas](https://avatars.discourse-cdn.com/v4/letter/i/f6c823/32.png) [@isaacsas](https://discourse.julialang.org/u/isaacsas)\
**Post date:** [May 24, 2022, 11:38pm UTC](https://discourse.julialang.org/t/type-instability-with-tuples-of-functions-and-parameters/81588/13 "2022-05-24T23:38:24Z")

</div>

Oh yeah, there is also [GitHub - tisztamo/FunctionWranglers.jl: Fast, inlined execution of arrays of functions](https://github.com/tisztamo/FunctionWranglers.jl) which I haven’t used but would be another option.

---

<div class="post-metadata">

**Author:** ![Vasily\_Pisarev](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/vasily_pisarev/32/7929_2.png) [@Vasily\_Pisarev](https://discourse.julialang.org/u/Vasily_Pisarev)\
**Post date:** [May 25, 2022, 6:30am UTC](https://discourse.julialang.org/t/type-instability-with-tuples-of-functions-and-parameters/81588/14 "2022-05-25T06:30:19Z")

</div>

Another option is to use `ntuple` instead of a loop with `zip`:

```julia
function TestFnc(pt::Point{T,N,FTup,PTup}) where {T,N,FTup,PTup} 
    res = sum(ntuple(i -> pt.F[i](pt.x, pt.y, pt.F_params[i]), length(pt.F)))
    return res
end

```

(`ntuple` should expand into recursion for small tuple, so that is unroll-friendly as well)
