Lazy evaluation of default parameter in clojure

77 Views Asked by At

I have created a function with an optional argument a with a default value corresponding to the result of a function call :

(defn mycall[]
  (print "*** called")
  100)

(defn hello [& {:keys [a] :or {a (mycall)}}]
              (print a))

Upon running my function, I find that the function call for default is evaluated EVEN if I provide a value for the argument 'a'

(hello :a 22)
*** called
22

Why does this happen? I would have expected not printout '*** called' and is there a way to not have the default function evaluation if I supply the named parameter?

Does anyone have any suggestion to implement this differently ? Many thanks.

1

There are 1 best solutions below

7
Eugene Pakhomov On

Why does this happen?

During destructuring, {:keys [a] :or {a (mycall)}} becomes something like

(let [a (get the-map-arg :a (mycall))]
  ...)

It uses the arity of get that accepts a default value. Since get is a plain function, its arguments always have to be evaluated before the function itself is called.

is there a way to not have the default function evaluation if I supply the named parameter?

Yes, albeit it's not that straightforward if you want for the default value to be evaluated only once:

(defn mycall[]
  (print "*** called")
  100)

(let [default-a' (delay (mycall))]
  (defn hello [& {:keys [a] :or {a ::default}}]
    (let [a (if (= a ::default)
              @default-a'
              a)]
      ...)))

If you don't care how many times the default value is evaluated or if you need to have it evaluated whenever :a is missing, simply remove the delay altogether and put (mycall) where @default-a' is.