TrivialCase
TrivialCase

Reputation: 1099

Clojure recur with multi-arity

I'm probably getting ahead of myself, I'm barely into functions on my first day of learning Clojure, but I thought I would be ambitious and make a recursive function to convert floating values to ternary. If I call the function by name instead of using recur it works great. I understand the problem is that I am just recuring the 1-arity version of the function, is there a standard way to handle recursion in multi-arity functions? The book I'm reading doesn't seem to cover it.

(defn float-to-ternary
  ([x k s]
    (def a (int x))
    (def r (- x a))
    (def carry-string (str s (. Integer toString a 3)))
    (cond
      (== r 0) carry-string
      (> k 20) carry-string
      :default (recur (* 3 r) (inc k) carry-string)
    )
  )
  ([x]
    (def a (int x))
    (def r (- x a))
    (def carry-string (str (. Integer toString a 3) "."))
    (cond
      (== r 0) (str (. Integer toString a 3))
      :default (recur (* 3 r) 1 carry-string)
    )
  )
)

Upvotes: 3

Views: 1086

Answers (1)

Carcigenicate
Carcigenicate

Reputation: 45726

If you want to "recur into a different arity", just explicitly call the function instead of using recur:

(defn float-to-ternary
  ([x k s]
   (def a (int x))
   (def r (- x a))
   (def carry-string (str s (. Integer toString a 3)))
   (cond
     (== r 0) carry-string
     (> k 20) carry-string
     :default (recur (* 3 r) (inc k) carry-string)))   

  ([x]
   (def a (int x))
   (def r (- x a))
   (def carry-string (str (. Integer toString a 3) "."))
   (cond
     (== r 0) (str (. Integer toString a 3))
     :default (float-to-ternary (* 3 r) 1 carry-string))))

This is safe. You "spend" one stack frame when you don't use recur, but the rest of the recursions use recur, so it's fine.


I have some obligatory suggestions too though:

  • Don't use def inside of functions unless you really have a good reason. def creates global variables that don't go out of scope when the function returns!

  • Your uses of cond are unnecessary.

    • In the first body, you want to return carry-string for the first two conditions. You could just make that one condition, connecting the two with an or, which lets you simply use if.

    • Since the second use only has two outcomes, if again makes more sense.

Taking this into consideration, your code would look more like:

(defn float-to-ternary
  ([x k s]
   (let [a (int x)
         r (- x a)
         carry-string (str s (. Integer toString a 3))]
     (if (or (> k 20) (== r 0))
      carry-string
      (recur (* 3 r) (inc k) carry-string))))

  ([x]
   (let [a (int x)
         r (- x a)
         carry-string (str (. Integer toString a 3) ".")]
     (if (== r 0)
       (str (. Integer toString a 3))
       (float-to-ternary (* 3 r) 1 carry-string)))))

Upvotes: 9

Related Questions