Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Reusing values in condition and consequent for efficiency in lisp / clojure

I have a cond, e.g of the form:

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

Say in condition2 I want to compute some value which is expensive, so I would prefer to only do it once. If condition2 is true then I would like to use this expensive value in consequent2. My dilemma is then that I don't want to recompute the value in the condition and consequent, since this is wasteful. I also don't want to throw the whole cond inside a larger let function, e.g.

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

since I don't want to compute this value if I never get to condition 2, i.e. if condition1 is true.

Is there an idiomatic way to deal with this? The first thing that comes to mind is to memoizing the expensive function, but there must be simpler solution.

like image 256
zenna Avatar asked Apr 02 '13 18:04

zenna


3 Answers

In On Lisp, Paul Graham describes macros for anaphoric variants of Common Lisp conditionals which bind the symbol 'it to the value of the condition expression. These macros obey the same evaluation semantics as the normal conditional forms, so from your example, condition2 will be evaluated after condition1 and only if condition1 is false. All conditions will be evaluated at most once. You can download On Lisp at http://www.paulgraham.com/onlisptext.html, and the code for the anaphoric macros is in figure 14.1 on page 191.

like image 189
Brian B Avatar answered Nov 16 '22 07:11

Brian B


A somewhat ugly solution that should work in Clojure is

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

This however requires condition1 to be evaluated twice.

Assuming that lisp / clojure in the heading means Clojure or (another) lisp, in Common Lisp you can do

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

but this will not work in Clojure at the local variable is immutable.

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

(let [v (atom nil)]
  (cond
    (condition1) (consequent1)
    (do (reset! v (expensive)) (condition2 @v)) (consequent2 @v)))
like image 4
Terje D. Avatar answered Nov 16 '22 08:11

Terje D.


Use a delay to compute something at most once and use it zero or more times:

(let [expensive-thing (delay do-expensive-computation)]
  (cond (condition1) (consequent1)
        (condition2 @expensive-thing) (consequent2 @expensive-thing)))
like image 3
amalloy Avatar answered Nov 16 '22 07:11

amalloy