Unexpected behavior when using recur in variational function

I wrote an answer for this problem when I needed to give a recursive function an optional parameter. I ended up with something like equivalent:

(defn func [a & [b?]]
  (if b?
    b?
    (recur a a)))

My intention was to b?act as an optional parameter. If it was not sent, it will be by default nilthrough destructuring.

Instead of working, this gave me an error:

(func 1)
UnsupportedOperationException nth not supported on this type: Long  clojure.lang.RT.nthFrom (RT.java:947)

After some debugging, I realized that for some reason, the rest parameter was not a list, as I expected, but simply a past number! The error arose because she tried to destroy the number.

I can fix this by getting rid of the wrapper list in the parameter list:

(defn func [a & b]
  ...

. , rest , b . "" , , :

(defn func2 [a & [b?]]
  (if b?
    b?
    (func2 a a)))

(func2 1)
=> 1

- , ?

+6
1

, -,

; Note that recur can be surprising when using variadic functions.

(defn foo [& args]
  (let [[x & more] args]
    (prn x)
    (if more (recur more) nil)))

(defn bar [& args]
  (let [[x & more] args]
    (prn x)
    (if more (bar more) nil)))

; The key thing to note here is that foo and bar are identical, except
; that foo uses recur and bar uses "normal" recursion. And yet...

user=> (foo :a :b :c)
:a
:b
:c
nil

user=> (bar :a :b :c)
:a
(:b :c)
nil

; The difference arises because recur does not gather variadic/rest args
; into a seq.

, .

+9

All Articles