Reuse of values ​​in state and therefore efficiency in lisp / clojure

I have cond, for example, of the form:

(cond (condition1) (consequent1) (condition2) (consequent2)) 

Say in condition2. I want to calculate some value that is expensive, so I would prefer only to do it once. If condition2 is true, I would like to use this expensive value in result2. My dilemma is that I do not want to recount the value in the condition and therefore because it is wasteful. I also don't want to throw the whole cond inside a larger let function, for example.

 (let [value-used-in-cond2 do-expensive-computation] (cond (condition1) (consequent1) (condition2) (consequent2))) 

since I do not want to calculate this value if I never go to condition 2, i.e. if condition1 is true.

Is there an idiomatic way to handle this? The first thing that comes to mind is a memory of an expensive feature, but there should be a simpler solution.

+6
source share
6 answers

In On Lisp, Paul Graham describes macros for anaphoric variants of Common Lisp conventions that associate the 'it character with the value of a condition expression. These macros obey the same evaluation semantics as regular conditional forms, so from your example, condition2 will be evaluated after condition1 and only if condition1 is false. All conditions will be evaluated no more than once. You can download On Lisp at http://www.paulgraham.com/onlisptext.html , and the code for anaphore macros can be found in Figure 14.1 on page 191.

+8
source

A somewhat ugly solution that should work in Clojure,

 (let [expensive-result (or (condition1) (do-expensive-computation)] (cond (condition1) (consequent1) (condition2) (consequent2))) 

This requires that condition 1 be checked twice.

Assuming lisp / Clojure in the header means Clojure or (other) lisp, in Common lisp you can do

 (let (var) (cond ((condition1) (consequent1)) ((setq var (condition2)) (consequent2)))) 

but this will not work in Clojure when the local variable is unchanged.

You can use an atom to achieve something similar with Clojure.

 (let [v (atom nil)] (cond (condition1) (consequent1) (do (reset! v (expensive)) (condition2 @v)) (consequent2 @v))) 
+4
source

Use delay to calculate something no more than once and use it zero or more times:

 (let [expensive-thing (delay do-expensive-computation)] (cond (condition1) (consequent1) (condition2 @expensive-thing) (consequent2 @expensive-thing))) 
+3
source

One way to rewrite this in Clojure to avoid repeating the calculation would be:

 (or (when (condition1) (consequent1)) (when-let [val2 (condition2)] (consequent2 val2))) 

This works by assuming that consequent1 and consequent2 never return nil - otherwise, the evaluation of or will fail in the following form.

+2
source

I had a similar problem, but I only had two cases, so I used a combination of the function and the if-let macro like this:

 (defn- expensive-computation [ab] (if (test (compute ab)) a nil)) (if-let [foo (expensive-computation ab)] (consequent2 foo) (consequent1)) 

As you can see, the value is calculated only once, after which a simple comparison is performed to check whether the calculation test was completed or not. If the test was not successful, it returns zero, thus performing 1. This is not a very clean solution, but it was the best I found. Hope this helps.

0
source

If condition2 uses an expensive value, and condition2 is false, I assume it has the form

 (and (expensive-calculation) (other-extra-condition)) 

What I do in these cases:

 (let (expensive-value) (cond (condition1 consequent1) (and (setq expensive-value (expensive-calculation)) (other-extra-condition)) consequent2) ((and expensive-value (other-extra-condition2)) consequent3) 
0
source

All Articles