Reputation:
lets say I have a side-effect free function that I use repeatedly with the same parameters without storing the results in a variable. Does Clojure notice this and uses the pre-calculated value of the function or is the value recalculated all the time?
Example:
(defn rank-selection [population fitness]
(map
#(select-with-probability (sort-by fitness population) %)
(repeatedly (count population) #(rand))))
(defn rank-selection [population fitness]
(let [sorted-population (sort-by fitness population)]
(map
#(select-with-probability sorted-population %)
(repeatedly (count population) #(rand)))))
In the first version sort-by
is executed n
-times (where n
is the size of the population).
In the second version sort-by
is executed once and the result is used n
-times
Does Clojure stores the result nonetheless? Are these methods comparable fast?
Upvotes: 2
Views: 174
Reputation: 9930
Clojure doesn't store the results unless you specify that in your code, either by using memoize
like mentioned in the comments or by saving the calculation/result in a local binding like you did.
Regarding the questions about how fast is one function regarding the other, here's some code that returns the time for the execution of each (I had to mock the select-with-probability
function). The doall
s are necessary to force the evaluation of the result of map
.
(defn select-with-probability [x p]
(when (< p 0.5)
x))
(defn rank-selection [population fitness]
(map
#(select-with-probability (sort-by fitness population) %)
(repeatedly (count population) rand)))
(defn rank-selection-let [population fitness]
(let [sorted-population (sort-by fitness population)]
(map
#(select-with-probability sorted-population %)
(repeatedly (count population) rand))))
(let [population (take 1000 (repeatedly #(rand-int 10)))]
(time (doall (rank-selection population <)))
(time (doall (rank-selection-let population <)))
;; So that we don't get the result seq
nil)
This returns the following in my local environment:
"Elapsed time: 95.700138 msecs"
"Elapsed time: 1.477563 msecs"
nil
In order to avoid the use of the let
form you could also use partial
which receives a function and any number of arguments, and returns a partial application of that function with the values of the arguments supplied. The performance of the resulting code is in the same order as the one with the let
form but is more succinct and readable.
(defn rank-selection-partial [population fitness]
(map
(partial select-with-probability (sort-by fitness population))
(repeatedly (count population) rand)))
(let [population (take 1000 (repeatedly #(rand-int 10)))]
(time (doall (rank-selection-partial population <)))
;; So that we don't get the result seq
nil)
;= "Elapsed time: 0.964413 msecs"
Upvotes: 1
Reputation: 96394
In Clojure sequences are lazy, but the rest of the language, including function evaluation, is eager. Clojure will invoke the function every time for you. Use the second version of your rank-selection function.
Upvotes: 1