Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Unexpected behavior when using recur in a variadic function

Tags:

clojure

I was writing an answer for this challenge, when I needed to give a recursive function an optional parameter. I ended up with something kind of equivalent to:

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

My intent was for b? to act as an optional parameter. If it wasn't supplied, it would be defaulted to nil via destructuring.

Instead of running though, it 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 wasn't a list as I'd expect, but just the passed number! The error was coming about because it tried to destructure the number.

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

(defn func [a & b]
  ...

But this just looks wrong. I know the rest parameter should be a list, but b is actually just a number. If I use "unoptimized" recursion, it works as I'd expect:

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

(func2 1)
=> 1

Can anyone explain what's going on here?

like image 329
Carcigenicate Avatar asked Sep 14 '17 20:09

Carcigenicate


1 Answers

This appears to be a known difference

; 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.

It's the last comment that describes the difference.

like image 156
John Szakmeister Avatar answered Oct 11 '22 16:10

John Szakmeister