More idiomatic way of phrasing `reduce(\circ, repeated(f, x))(y)`?

A bit late to the party, but the closest thing to what you are doing is to use foldl with |> instead of creating the composite function. For instance

val = 1.2
n = 5
foldl(|>, Iterators.repeated(exp, n), init = val)

I haven’t benchmarked it, but I’ve found foldl(|>, fs, init = val) to be a pretty useful pattern to compose a list of functions, but of course the other approach with (y, _) -> exp(y) also works if all the functions are equal.

nice, looks like that’s basically the same as the other best ones and is much easier to read:

master:

julia> f(a) = foldl(|>, repeated(exp, 1000); init=a)
f (generic function with 3 methods)

julia> @benchmark f(10.)
BenchmarkTools.Trial: 10000 samples with 999 evaluations per sample.
 Range (min … max):  8.245 ns … 184.536 ns  ┊ GC (min … max): 0.00% … 0.00%
 Time  (median):     9.232 ns               ┊ GC (median):    0.00%
 Time  (mean ± σ):   9.830 ns ±   3.589 ns  ┊ GC (mean ± σ):  0.00% ± 0.00%

  ▃   ▄▇█▇▃         ▅▃    ▂▁                                  ▂
  █▆▄▄██████▇▆▆▄▄▄▆▄███▇▇▆██▇▆▆▆▆▆▆▆▆▆▆▅▆▇▆▇▇▆▆▆▅▅▅▆▆▆▄▅▄▄▅▄▅ █
  8.25 ns      Histogram: log(frequency) by time      16.7 ns <

 Memory estimate: 0 bytes, allocs estimate: 0.

julia> @benchmark f($10.)
BenchmarkTools.Trial: 10000 samples with 5 evaluations per sample.
 Range (min … max):  5.883 μs … 112.796 μs  ┊ GC (min … max): 0.00% … 0.00%
 Time  (median):     7.540 μs               ┊ GC (median):    0.00%
 Time  (mean ± σ):   7.786 μs ±   2.355 μs  ┊ GC (mean ± σ):  0.00% ± 0.00%

  ▂  ▅▅   █▂   ▁▁                  ▂▁                         ▁
  █▅▂██▇▆▇██▇███████▇▇▇▆▆▆▆▄▅▅▅▆▆▅▆██▆▅▅▅▅▃▄▅▅▆▄▃▂▃▂▅▃▃▃▂▂▃▃▅ █
  5.88 μs      Histogram: log(frequency) by time      17.1 μs <

 Memory estimate: 0 bytes, allocs estimate: 0.

As DNF suggested, here is a simple loop implementation:

julia> iterated_fnx(f,n,x) = ( while (n -= 1) ≥ 0; x = f(x); end; return x)
iterated_fnx (generic function with 1 method)

julia> iterated_fnx(exp, 3, 1.0)
3.814279104760214e6

It’s compacted a bit, but still as legible as the alternatives IMHO.
It benchmarks with no allocations on Julia 1.11.

while loop appears to be a bit slower, maybe since you’ve got an extra n-=1 operation and > check each time:

julia> f1(a) = foldl(|>, repeated(exp, 1000); init=a)
f1 (generic function with 1 method)

julia> f2(a) = (for _=1:1000 a=exp(a) end; a)
f2 (generic function with 1 method)

julia> f3(a) = (n=1000; while (n-=1)≥0 a=exp(a) end; a)
f3 (generic function with 1 method)

julia> @benchmark f1($10.)
BenchmarkTools.Trial: 10000 samples with 5 evaluations per sample.
 Range (min … max):  6.554 μs …  25.477 μs  ┊ GC (min … max): 0.00% … 0.00%
 Time  (median):     6.665 μs               ┊ GC (median):    0.00%
 Time  (mean ± σ):   6.824 μs ± 864.537 ns  ┊ GC (mean ± σ):  0.00% ± 0.00%

  ▆█▄▁                                                        ▁
  █████▇▇▆▆▆▇▇██▇▇▆▆▆▆▆▆▅▅▆▆▆▆▆▆▆▆▅▅▆▅▅▅▅▅▄▅▅▅▄▄▄▄▄▅▄▂▂▃▄▄▃▃▃ █
  6.55 μs      Histogram: log(frequency) by time      10.6 μs <

 Memory estimate: 0 bytes, allocs estimate: 0.

julia> @benchmark f2($10.)
BenchmarkTools.Trial: 10000 samples with 5 evaluations per sample.
 Range (min … max):  6.564 μs …  25.035 μs  ┊ GC (min … max): 0.00% … 0.00%
 Time  (median):     6.642 μs               ┊ GC (median):    0.00%
 Time  (mean ± σ):   6.868 μs ± 967.479 ns  ┊ GC (mean ± σ):  0.00% ± 0.00%

  ██▂▁                                                        ▁
  █████▇▇▆▆▆▆▆▆▅▄▅▅▆▇▇▇▆▇▆▆▆▆▆▅▆▅▅▆▆▅▇▆▆▅▆▆▆▆▄▅▆▆▆▆▆▅▆▅▅▄▅▅▅▄ █
  6.56 μs      Histogram: log(frequency) by time        11 μs <

 Memory estimate: 0 bytes, allocs estimate: 0.

julia> @benchmark f3($10.)
BenchmarkTools.Trial: 10000 samples with 5 evaluations per sample.
 Range (min … max):  7.518 μs …  29.437 μs  ┊ GC (min … max): 0.00% … 0.00%
 Time  (median):     7.546 μs               ┊ GC (median):    0.00%
 Time  (mean ± σ):   7.719 μs ± 912.501 ns  ┊ GC (mean ± σ):  0.00% ± 0.00%

  █▄▂▂▁▁                                                      ▁
  ██████████▇█▇▆▆▇▆▇▆▆▆▆▆▆▆▅▅▆▆▆▆▆▆▅▅▅▅▆▅▆▅▅▆▅▅▅▅▅▅▄▅▄▄▃▃▅▅▄▅ █
  7.52 μs      Histogram: log(frequency) by time      10.1 μs <

 Memory estimate: 0 bytes, allocs estimate: 0.

if you specifically define all three as variables, it becomes equivalent though:

julia> @benchmark f1(exp, $10., 1000)
BenchmarkTools.Trial: 10000 samples with 5 evaluations per sample.
 Range (min … max):  5.942 μs …  33.609 μs  ┊ GC (min … max): 0.00% … 0.00%
 Time  (median):     6.646 μs               ┊ GC (median):    0.00%
 Time  (mean ± σ):   6.762 μs ± 646.787 ns  ┊ GC (mean ± σ):  0.00% ± 0.00%

           ▅▇█▄▁       ▁▁▁                                    ▂
  ▅▁▁▁▁▃▁▁▁███████▇▇█▇██████▆▆▆▇▇▇▆▇▇▆▆▅▆▆▆▅▅▅▅▅▆▆▅▅▅▅▅▅▄▄▅▅▅ █
  5.94 μs      Histogram: log(frequency) by time      9.67 μs <

 Memory estimate: 0 bytes, allocs estimate: 0.

julia> @benchmark f2(exp, $10., 1000)
BenchmarkTools.Trial: 10000 samples with 5 evaluations per sample.
 Range (min … max):  5.962 μs …  24.994 μs  ┊ GC (min … max): 0.00% … 0.00%
 Time  (median):     6.680 μs               ┊ GC (median):    0.00%
 Time  (mean ± σ):   6.927 μs ± 966.049 ns  ┊ GC (mean ± σ):  0.00% ± 0.00%

         ▇▇█▂     ▁▁▁      ▁▂▁                   ▃            ▂
  ▃▁▃▁▁▁▁█████▇▇▆█████▇▆▇▇▇███▆▇▆▆▆█▇█▆▆▇▇▆▅▆▆▅▆▆█▅▅▅▄▄▅▄▅▄▅▅ █
  5.96 μs      Histogram: log(frequency) by time      10.6 μs <

 Memory estimate: 0 bytes, allocs estimate: 0.

julia> @benchmark f3(exp, $10., 1000)
BenchmarkTools.Trial: 10000 samples with 5 evaluations per sample.
 Range (min … max):  5.973 μs …  36.853 μs  ┊ GC (min … max): 0.00% … 0.00%
 Time  (median):     6.675 μs               ┊ GC (median):    0.00%
 Time  (mean ± σ):   6.804 μs ± 796.739 ns  ┊ GC (mean ± σ):  0.00% ± 0.00%

           ▇██▃▁       ▁▁                                     ▂
  ▃▁▁▁▁▁▁▃▁██████▇█▇▇██████▇▆▇▆▇▅▆▆▆▆▆▆▆▇▅▆▆▅▆▅▅▆▅▅▅▅▆▆▄▅▅▅▅█ █
  5.97 μs      Histogram: log(frequency) by time      9.74 μs <

 Memory estimate: 0 bytes, allocs estimate: 0.

You can frequently get a free speed boost by sprinkling @inline there, if it’s a rather heavy function that doesn’t automatically get inlined.

f2(f, a, n) = (for _=1:n a=f(a) end; a)
f3(f, a, n) = (for _=1:n a=@inline(f(a)) end; a)
julia> @benchmark f2(exp, x, 1000) setup=(x=rand())
BenchmarkTools.Trial: 10000 samples with 9 evaluations per sample.
 Range (min … max):  2.456 μs …   9.911 μs  ┊ GC (min … max): 0.00% … 0.00%
 Time  (median):     2.478 μs               ┊ GC (median):    0.00%
 Time  (mean ± σ):   2.605 μs ± 269.830 ns  ┊ GC (mean ± σ):  0.00% ± 0.00%

  █▇          ▁▇▆ ▁▂          ▅ ▁▁                            ▂
  ██▄▁▅▁▃▁▁▃▄▇███▅██▇▆▅▆▆▆▆▇▅▅█▅██▆▅▁▇█▇▆▆▇▅▇▆▆▅▅▆▆▇▃▅▄▅▃▃▄▁▆ █
  2.46 μs      Histogram: log(frequency) by time      3.39 μs <

 Memory estimate: 0 bytes, allocs estimate: 0.

julia> @benchmark f3(exp, x, 1000) setup=(x=rand())
BenchmarkTools.Trial: 10000 samples with 9 evaluations per sample.
 Range (min … max):  2.022 μs …   8.378 μs  ┊ GC (min … max): 0.00% … 0.00%
 Time  (median):     2.033 μs               ┊ GC (median):    0.00%
 Time  (mean ± σ):   2.063 μs ± 170.374 ns  ┊ GC (mean ± σ):  0.00% ± 0.00%

  ▅█▄                  ▂▁                                     ▁
  ███▄▅██▃▃▅▃▁▁▁▁▃▅▇▆▃▆██▁▇▆▆▅▆▆▆▅▅▆▅▁▅▃▅▃▅▃▃▄▁▁▁▁▃▁▁▁▁▄██▆▆▅ █
  2.02 μs      Histogram: log(frequency) by time      2.62 μs <

 Memory estimate: 0 bytes, allocs estimate: 0.