# Type stability when arguments are functions

**URL:** <https://discourse.julialang.org/t/type-stability-when-arguments-are-functions/4734>\
**Category:** General Usage\
**Created:** [July 8, 2017, 12:16am UTC](https://discourse.julialang.org/t/type-stability-when-arguments-are-functions/4734 "2017-07-08T00:16:29Z")\
**Posts on this page:** 8\
**Page:** 1

<div class="post-metadata">

**Author:** ![dpo](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/dpo/32/3335_2.png) [@dpo](https://discourse.julialang.org/u/dpo)\
**Post date:** [July 8, 2017, 12:16am UTC](https://discourse.julialang.org/t/type-stability-when-arguments-are-functions/4734/1 "2017-07-08T00:16:30Z")

</div>

Sorry if this has been asked before. I have functions that take vectors of functions as arguments, similarly to the following:

```julia
function g2(fs::Vector{Function}, x::Float64)::Float64
       a::Float64 = 0.0
       for f in fs
       a += f(x)
       end
       a
end

```

Let’s say we define

```julia
julia> f1(x::Float64)::Float64 = sin(x);
julia> f2(x::Float64)::Float64 = cos(x);
julia> f3(x::Float64)::Float64 = tan(x);

```

Do the `Any` in the following `@code_warntype` indicate type instability? If so, is there a way to make `g2` type stable?

```julia
julia> @code_warntype g2([f1, f2, f3], 3.14)
Variables:
  #self#::#g2
  fs::Array{Function,1}
  x::Float64
  a::Float64
  #temp#::Int64
  f::F

Body:
  begin 
      SSAValue(0) = Main.Float64
      SSAValue(1) = 0.0
      a::Float64 = SSAValue(1) # line 3:
      #temp#::Int64 = $(QuoteNode(1))
      6: 
      unless (Base.box)(Base.Bool,(Base.not_int)((#temp#::Int64 === (Base.box)(Int64,(Base.add_int)((Base.arraylen)(fs::Array{Function,1})::Int64,1)))::Bool)) goto 17
      SSAValue(5) = (Base.arrayref)(fs::Array{Function,1},#temp#::Int64)::F
      SSAValue(6) = (Base.box)(Int64,(Base.add_int)(#temp#::Int64,1))
      f::F = SSAValue(5)
      #temp#::Int64 = SSAValue(6) # line 4:
      SSAValue(4) = (a::Float64 + (f::F)(x::Float64)::Any)::Any
      a::Float64 = (Core.typeassert)((Base.convert)(Main.Float64,SSAValue(4))::Any,Main.Float64)::Float64
      15: 
      goto 6
      17: # line 6:
      return a::Float64
  end::Float64

```

Is there a way to indicate that the input argument `fs` should be a vector of `Functions` each with a certain return type?

Thanks!

---

<div class="post-metadata">

**Author:** ![kristoffer.carlsson](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/kristoffer.carlsson/32/22_2.png) [@kristoffer.carlsson](https://discourse.julialang.org/u/kristoffer.carlsson)\
**Post date:** [July 8, 2017, 12:31am UTC](https://discourse.julialang.org/t/type-stability-when-arguments-are-functions/4734/2 "2017-07-08T00:31:26Z")

</div>

FunctionWrappers.jl could perhaps be of interest. Note that your function is type stable (because of the type assert) but the result of `f` is not.

---

<div class="post-metadata">

**Author:** ![nalimilan](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/nalimilan/32/147_2.png) [@nalimilan](https://discourse.julialang.org/u/nalimilan)\
**Post date:** [July 8, 2017, 10:29am UTC](https://discourse.julialang.org/t/type-stability-when-arguments-are-functions/4734/3 "2017-07-08T10:29:44Z")

</div>

You could replace `fs::Vector{Function}` with `fs::Tuple{Vararg{Function}` and pass a tuple of functions rather than an array. That will allow the compiler to know the exact types of the functions. Unfortunately, the return types do not seem to be inferred in that case: the fact that there is a loop over the functions does not appear to be handled (yet).

---

<div class="post-metadata">

**Author:** ![tim.holy](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/tim.holy/32/52_2.png) [@tim.holy](https://discourse.julialang.org/u/tim.holy)\
**Post date:** [July 8, 2017, 11:14am UTC](https://discourse.julialang.org/t/type-stability-when-arguments-are-functions/4734/4 "2017-07-08T11:14:08Z")

</div>

Try “lispy tuple recursion,”

```julia
@inline g2(fs::Tuple{Function, Vararg{Function}}, x::Float64) = _g2(0.0, fs, x)
# _gs is a "private" method that processes the first call, then discards that function and recursively calls itself 
@inline _g2(a, fs::Tuple{Function, Vararg{Function}}, x::Float64) = _g2(a + fs[1](x), Base.tail(fs), x)
# But we have to terminate the recursion. This method is called when we've "used up" all the functions
_g2(a, ::Tuple{}, x::Float64) = a

@code_warntype g2((sin, cos, tan), 3.14)

```

You’ll see this is type-stable even without any of the type-assertions/declarations.

---

<div class="post-metadata">

**Author:** ![kristoffer.carlsson](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/kristoffer.carlsson/32/22_2.png) [@kristoffer.carlsson](https://discourse.julialang.org/u/kristoffer.carlsson)\
**Post date:** [July 8, 2017, 11:51am UTC](https://discourse.julialang.org/t/type-stability-when-arguments-are-functions/4734/5 "2017-07-08T11:51:08Z")

</div>

How does compile time scale with the number of functions and is there a threshold where this does not work? Would try it myself but I don’t have access to a computer for a while.

---

<div class="post-metadata">

**Author:** ![tim.holy](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/tim.holy/32/52_2.png) [@tim.holy](https://discourse.julialang.org/u/tim.holy)\
**Post date:** [July 8, 2017, 12:01pm UTC](https://discourse.julialang.org/t/type-stability-when-arguments-are-functions/4734/6 "2017-07-08T12:01:18Z")

</div>

> [@kristoffer.carlsson](#):
>
> How does compile time scale with the number of functions

O(N^2)

> is there a threshold where this does not work

Set by `MAX_TUPLETYPE_LEN`. Currently 15, but Jameson has shown that recent improvements to inference.jl may allow that to be changed to \>1000.

---

<div class="post-metadata">

**Author:** ![bramtayl](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/bramtayl/32/3614_2.png) [@bramtayl](https://discourse.julialang.org/u/bramtayl)\
**Post date:** [July 8, 2017, 1:35pm UTC](https://discourse.julialang.org/t/type-stability-when-arguments-are-functions/4734/7 "2017-07-08T13:35:01Z")

</div>

This seems like a pretty common pattern in high performance julia. I wonder if there’s a way to get it into a macro.

---

<div class="post-metadata">

**Author:** ![cstjean](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/cstjean/32/1444_2.png) [@cstjean](https://discourse.julialang.org/u/cstjean)\
**Post date:** [July 8, 2017, 2:10pm UTC](https://discourse.julialang.org/t/type-stability-when-arguments-are-functions/4734/8 "2017-07-08T14:10:22Z")

</div>

I’ve got an unregistered package [Unrolled.jl](https://github.com/cstjean/Unrolled.jl#unrolled-functions) with generated functions that achieve the same thing, but without the quadratic compile-time, and for arbitrary-long tuples:

```julia
julia> using Unrolled, BenchmarkTools

julia> g2(fs::Tuple, x::Float64) =
           unrolled_reduce(+, 0.0, unrolled_map(f->f(x), fs))
g2 (generic function with 1 method)

julia> @benchmark g2((sin, cos, tan), 10.0)
BenchmarkTools.Trial: 
  memory estimate: 0 bytes
  allocs estimate: 0
  --------------
  minimum time: 58.724 ns (0.00% GC)
  median time: 58.822 ns (0.00% GC)
  mean time: 63.931 ns (0.00% GC)
  maximum time: 280.161 ns (0.00% GC)
  --------------
  samples: 10000
  evals/sample: 984

julia> g3(x) = sin(x) + cos(x) + tan(x)
g3 (generic function with 1 method)

julia> @benchmark g3(10.0)
BenchmarkTools.Trial: 
  memory estimate: 0 bytes
  allocs estimate: 0
  --------------
  minimum time: 54.456 ns (0.00% GC)
  median time: 54.538 ns (0.00% GC)
  mean time: 62.114 ns (0.00% GC)
  maximum time: 269.921 ns (0.00% GC)
  --------------
  samples: 10000
  evals/sample: 985

```

The definitions are straight-forward, as far as these things go:

```julia
@generated function unrolled_map(f, seq::Tuple) 
    :(tuple($((:(f(seq[$i])) for i in 1:type_length(seq))...)))
end

@generated function unrolled_reduce(f, v0, seq) 
    niter = type_length(seq)
    expand(i) = i == 0 ? :v0 : :(f(seq[$i], $(expand(i-1))))
    return expand(niter)
end

```
