You might want foldl()
or foldr()
instead of reduce
, since reduce
does not make any guarantees about its associativity.
It’s also worth noting that you actually get good performance using the chained function without needing to use FunctionWrappers or anything like it:
julia> f1(x) = x^1
f1 (generic function with 1 method)
julia> f2(x) = x^2
f2 (generic function with 1 method)
julia> f3(x) = x^3
f3 (generic function with 1 method)
julia> f4(x) = x^4
f4 (generic function with 1 method)
julia> f = foldl(∘, [f1, f2, f3, f4])
#58 (generic function with 1 method)
julia> using BenchmarkTools
julia> @btime $f($(1.0))
21.348 ns (0 allocations: 0 bytes)
1.0
What’s happening here is that the return type of foldl
itself cannot be inferred (because it depends on the non-concrete element types of [f1, f2, f3, f4]
, but the resulting function that it returns has no such limitations:
julia> @code_warntype foldl(∘, [f1, f2, f3, f4])
Variables
#self#::Core.Compiler.Const(foldl, false)
op::Core.Compiler.Const(∘, false)
itr::Array{Function,1}
Body::Any
1 ─ %1 = Core.NamedTuple()::Core.Compiler.Const(NamedTuple(), false)
│ %2 = Base.pairs(%1)::Core.Compiler.Const(Base.Iterators.Pairs{Union{},Union{},Tuple{},NamedTuple{(),Tuple{}}}(), false)
│ %3 = Base.:(#foldl#189)(%2, #self#, op, itr)::Any
└── return %3
julia> @code_warntype f(1.0)
Variables
#self#::Core.Compiler.Const(getfield(Base, Symbol("##58#59")){getfield(Base, Symbol("##58#59")){getfield(Base, Symbol("##58#59")){typeof(f1),typeof(f2)},typeof(f3)},typeof(f4)}(getfield(Base, Symbol("##58#59")){getfield(Base, Symbol("##58#59")){typeof(f1),typeof(f2)},typeof(f3)}(getfield(Base, Symbol("##58#59")){typeof(f1),typeof(f2)}(f1, f2), f3), f4), false)
x::Tuple{Float64}
Body::Float64
1 ─ %1 = Core.getfield(#self#, :f)::Core.Compiler.Const(getfield(Base, Symbol("##58#59")){getfield(Base, Symbol("##58#59")){typeof(f1),typeof(f2)},typeof(f3)}(getfield(Base, Symbol("##58#59")){typeof(f1),typeof(f2)}(f1, f2), f3), false)
│ %2 = Core.getfield(#self#, :g)::Core.Compiler.Const(f4, false)
│ %3 = Core._apply(%2, x)::Float64
│ %4 = (%1)(%3)::Float64
└── return %4