Is there a way to repeatedly call a function

Say I have a function f, I want to call it repeatedly n times. I.e. for n = 3, I want f ∘ f ∘ f. Is there a nice notation to do this for n not known at compile time, ideally something like f^n.

I can’t think of syntaxic sugar that would allow this in Julia. The prettiest way I could think of would be a recursive function :

f_rec(x, n) = (@assert n >= 0; n == 0 ? x : f_rec(f(x), n-1))

Although I heard recursive functions can actually be slow, so I don’t know if this is recommended.

Another way would be metaprogramming, although I don’t find this very clean…

# f^n(x) with n and x defined beforehand
"x" * reduce(*, [" |> f" for _ in 1:n]) |> Meta.parse |> eval

This doesn’t look right. You haven’t defined f. Something like

apply(f, x, n::Integer) = ((n > 0) ? f(apply(f, x, n-1)) : x)

and, yeah, probably throw an error for negative n.

1 Like

Yes, I assumed it was defined before indeed.

You can do a one-liner like

julia> ↑(f, n) = x -> foldl(|>, fill(f, n), init = x)
↑ (generic function with 1 method)

julia> (sqrt ↑ 3)(2)
1.0905077326652577

but the fill is wasteful so it can be implemented more efficiently.

2 Likes

Recursion can be slow for large n, and at worst you can get a stack overflow. For small n it should be fine.

For large n, an iterative approach should be better, e.g.

function apply(f, n, x::Integer)
    if n < 0
        error("n must be >= 0.")
    elseif n == 0
        return x
    end
    y = f(x)
    for _ in 2:n
        y = f(y)
    end
    return y
end

Even so, it would only work for one specific, named function, f.

1 Like

Maybe this: ?

1 Like

This looks like the winner to me. Applied to OP’s case it’s simply:

using IterTools

# Recursively apply function f to value x n times
fn(n,x) = nth(iterated(f,x),n)
4 Likes

We had discussed supporting f^n, but there wasn’t a consensus in favor:

1 Like

Another suggested previously is

foldl(|>, Iterators.repeated(f, n))

Edit: Better (as suggested below) is either

F(f,n)=foldl(∘, Iterators.repeated(f, n))

for just the iterated function, or if including the function argument,

F(f,n,x) =foldl(|>, Iterators.repeated(f, n); init=x)
1 Like

This is how I do it

    #=
       func_compose(func,n)

    Compose a function multiple times
    =#
    function func_compose(func,n)
        newfunc = func
        local loop = n
        while loop > 1
            #  help?> ∘
            #  "∘" can be typed by \circ<tab>
            newfunc = func ∘ newfunc
            loop -= 1
        end
        return newfunc
    end

f = func_compose(sqrt,5)
println("f(123_456_789.0) = ",f(123_456_789.0))
1 Like

F(f,n)=foldl(∘, Iterators.repeated(f, n))


F(f,n)=foldl(∘, ntuple(i->f,n))


F(f, n)=n>1 ? ∘(f,F(f,n-1)) : f

I think you forgot the ;init = x parameter.

More options:

F(f,n) = x->(for _ in 1:n x = f(x) ; end; x)  # returns function
F(f,n,x) = (for _ in 1:n x = f(x) ; end; x)

Maybe not very tricky, but do the work without allocating on 1.9.4.