6

I'm trying to make a function that compose a function f(x) by itself N times, something like that:

function CompositionN(f,N)
             for i in 1:N
                f(x) = f(f(x))
             end
return f(x)

I need that the function CompositionN returns another function, not a value.

3 Answers3

4

You can take advantage of the function, which allows you to compose multiple functions:

julia> composition(f, n) = ∘(ntuple(_ -> f, n)...)
composition (generic function with 1 method)

julia> composition(sin, 3)(3.14)
0.001592651569876818

julia> sin(sin(sin(3.14)))
0.001592651569876818
giordano
  • 8,087
  • 3
  • 23
  • 48
  • 2
    This works really well for small `n`, up to 10 on my computer. Then it suddenly becomes slow. For large `n` this seems to work better: `compose(f, n) = reduce(∘, ntuple(_ -> f, n))`. – DNF Oct 27 '20 at 22:07
  • @giordano How can I compose several functions that have as input and as output 2 variables? E.g. `f= (x,y)->(x+y,x*y); ∘(f,f,f)(1,1)` returns errors instead of `(5,6)`. – Leo Jul 27 '21 at 13:00
  • @Leo: your function composition works if it is not anonymous, but named: `f(x, y) = (x+y, x*y)`. – Jake Ireland Jan 14 '23 at 05:57
  • @JakeIreland And there's no syntax to make it work also for nameless functions? – Leo Jan 16 '23 at 18:18
4

The solution with ntuple and splatting works very well up to a certain number of compositions, say 10, and then falls off a performance cliff.

An alternative solution, using reduce, is fast for large numbers of compositions, n, but comparatively slow for small numbers:

compose_(f, n) = reduce(∘, ntuple(_ -> f, n))

I think that the following solution is optimal for both large and small n:

function compose(f, n)
    function (x)  # <- this is syntax for an anonymous function
        val = f(x)
        for _ in 2:n
            val = f(val)
        end
        return val
    end
end

BTW: it is the construction of the composed function which is faster in the approach suggested here. The runtimes of the resulting functions seem to be identical.

DNF
  • 11,584
  • 1
  • 26
  • 40
1

Here's a recursive approach:

julia> compose(f, n) = n <= 1 ? f : f ∘ compose(f, n-1)
compose (generic function with 1 method)

julia> compose(x -> 2x, 3)(1)
8

If we're willing to do a little type piracy, we can use the power operator ^ on functions to represent n-th order self-composition:

julia> Base.:^(f::Union{Type,Function}, n::Integer) = n <= 1 ? f : f ∘ f^(n-1)

julia> f(x) = 2x
f (generic function with 1 method)

julia> (f^3)(1)
8
Cameron Bieganek
  • 7,208
  • 1
  • 23
  • 40