Performance issues when defining functions in the `let` form

Is there any performance limitation for defining anonymous functions in the let form and calling an external function again? Like this:

 (defn bar [xy] (let [foo (fn [x] (* xx))] (+ (foo x) (foo y)))) 

Compared to defining foo as a separate function, for example:

 (defn foo [x] (* xx)) (defn bar [xy] (+ (foo x) (foo y))) 

I realized that the lexical domain foo is different in these two cases, I just wonder if the function foo will be defined again and again when bar called several times.

I think the answer is no, i.e. there is no penalty for this, but how did clojure do it?

Thanks!

+4
source share
2 answers

let local approach:

foo compiles only once (when the top level form). The result of this compilation is a class that implements the clojure.lang.IFn interface; the actual body lives in the invoke(Object) method of this class. At runtime, each time control reaches a point in bar , where local foo is entered, a new instance of this class is provided; two foo calls use this instance of this class.

Here is an easy way to prove the "single compilation" property in REPL:

 (defn bar [xy] (let [foo (fn [x] (* xx))] foo)) (identical? (class (bar 1 2)) (class (bar 1 2))) ;= true 

NB. Clojure is smart enough to notice that foo not an “actual closure” (it closes the bar parameters, but doesn’t actually use them), so the foo runtime view doesn’t work transferring any additional fields that would close, but a new instance of the class foo nonetheless stands out with every bar call.

Separate defn approach:

There is one instance of foo , however, calling it involves an indirect pass through Var, which in itself has a non-zero value. This cost, as a rule, is not worth worrying about everything except the most performance-sensitive code, but it is there, so factoring a local function may not necessarily be a victory in performance. As usual, if it's worth it to optimize, you should first evaluate / compare.

let over lambda

There is also the final version mentioned by Daniel, where let passes, not inside, defn . With this approach, there is one instance (class) of foo ; it is stored in a field inside bar ; and it is used for all foo calls inside bar .

+6
source

Yes, there will be a punishment for this, but, as Michael points out in the let local approach section, he is minimal.

If you need a lexical scope as you have described, wrap the let block defining foo around defn for bar. This will do what you ask. However, this is a pattern that is not commonly seen in Clojure code.

+1
source

All Articles