clojure - Iteratively apply function to its result without generating a seq -
this 1 of "is there built-in/better/idiomatic/clever way this?" questions.
i want function--call fn-pow
--that apply function f result of applying f argument, apply result of applying result, etc., n
times. example,
(fn-pow inc 0 3)
would equivalent to
(inc (inc (inc 0)))
it's easy iterate
:
(defn fn-pow-0 [f x n] (nth (iterate f x) n))
but creates , throws away unnecessary lazy sequence.
it's not hard write function scratch. here 1 version:
(defn fn-pow-1 [f x n] (if (> n 0) (recur f (f x) (dec n)) x))
i found twice fast fn-pow-0
, using criterium on (fn-pow inc 0 10000000)
.
i don't consider definition of fn-pow-1
unidiomatic, fn-pow
seems might standard built-in function, or there may simple way define couple of higher-order functions in clever arrangement. haven't succeeded in discovering either. missing something?
the built-in looking dotimes
. i'll tell why in round-about fashion.
time
what testing in benchmark overhead of level of indirection. (nth (iterate ...) n)
only twice slow compiles loop when body fast function rather surprising/encouraging. if f
more costly function, importance of overhead diminishes. (of course if f
low-level , fast, should use low-level loop construct.)
say function takes ~ 1 ms instead
(defn my-inc [x] (thread/sleep 1) (inc x))
then both of these take 1 second -- difference around 2% rather 100%.
(bench (fn-pow-0 my-inc 0 1000)) (bench (fn-pow-1 my-inc 0 1000))
space
the other concern iterate
creating unnecessary sequence. but, if not holding onto head, doing nth
, aren't really creating sequence per se sequentially creating, using, , discarding lazyseq
objects. in other words, using constant amount of space, though generating garbage in proportion n
. however, unless f
primitive or mutating argument, already producing garbage in proportion n
in producing own intermediate results.
reducing garbage
an interesting compromise between fn-pow-0
, fn-pow-1
(defn fn-pow-2 [f x n] (reduce (fn [x _] (f x)) x (range n)))
since range
objects know how intelligently reduce themselves, not create additional garbage in proportion n
. boils down loop well. reduce
method of range:
public object reduce(ifn f, object start) { object ret = f.invoke(start,n); for(int x = n+1;x < end;x++) ret = f.invoke(ret, x); return ret; }
this fastest of 3 (before adding primitive type-hints on n
in recur
version, is) slowed down my-inc
.
mutation
if iterating function potentially expensive in time or space, such matrix operations, may wanting use (in contained manner) f
mutates argument eliminate garbage overhead. since mutation side effect, , want side effect n
times, dotimes
natural choice.
for sake of example, i'll use atom
stand-in, imagine bashing on mutable matrix instead.
(def my-x (atom 0)) (defn my-inc! [x] (thread/sleep 1) (swap! x inc)) (defn fn-pow-3! [f! x n] (dotimes [i n] (f! x)))
Comments
Post a Comment