clojure recur vs imperative loop

前端 未结 2 1639
面向向阳花
面向向阳花 2021-02-04 13:18

Learning Clojure and trying to understand the implementation:

What\'s the difference from:

(def factorial
  (fn [n]
    (loop [cnt n acc 1]
       (if (z         


        
2条回答
  •  走了就别回头了
    2021-02-04 13:40

    Are Clojure's loop and recur forms specifically designed to code a simple imperative loop?

    Yes.

    In functional terms:

    • A loop is a degenerate form of recursion called tail-recursion.
    • The 'variables' are not modified in the body of the loop. Instead, they are re-incarnated whenever the loop is re-entered.

    Clojure's recur makes a tail-recursive call to the surrounding recursion point.

    • It re-uses the one stack frame, so working faster and avoiding stack overflow.
    • It can only happen as the last thing to do in any call - in so-called tail position.

    Instead of being stacked up, each successive recur call overwrites the last.

    A recursion point is

    • a fn form, possibly disguised in defn or letfn OR
    • a loop form, which also binds/sets-up/initialises the locals/variables.

    So your factorial function could be re-written

    (def factorial
      (fn [n]
        ((fn fact [cnt acc]
          (if (zero? cnt)
            acc
            (fact (dec cnt) (* acc cnt))))
         n 1)))
    

    ... which is slower, and risks stack overflow.

    Not every C/C++ loop translates smoothly. You can get trouble from nested loops where the inner loop modifies a variable in the outer one.


    By the way, your factorial function

    • will cause integer overflow quite quickly. If you want to avoid this, use 1.0 instead of 1 to get floating point (double) arithmetic, or use *' instead of * to get Clojure's BigInt arithmetic.
    • will loop endlessly on a negative argument.

    A quick fix for the latter is

    (def factorial
      (fn [n]
        (loop [cnt n acc 1]
          (if (pos? cnt)
            (recur (dec cnt) (* acc cnt))
            acc))))
    ; 1
    

    ... though it would be better to return nil or Double.NEGATIVE_INFINITY.

提交回复
热议问题