Does Clojure have short-circuit logic?
Asked Answered
S

6

5

In many languages, if you write something along the lines of

if (foo() || bar() || foobar()) { /* do stuff */ }

and foo() returns true, then bar() and foobar() will not be evaluated.

Suppose I had the following Clojure code:

(let [a (simple-function args)
      b (complex-function args)
      c (too-lazy-to-optimize-this-function args)]
  (or a b c))

If a evaluates to true, will b and c also be evaluated, or will they be ignored?

Thanks!

Scarlettscarp answered 18/11, 2011 at 23:28 Comment(0)
H
13

Since you answered your own question, note that though in your example b and c may not evaluated in the (or a b c) call, the let binding is evaluated before that so the too-lazy-to-optimize-this-function call is evaluated anyway. Clojure isn't as lazy as that.

To be clear: to conditionally evaluate the function calls, you need to put the expression evaluating them in the or call, basically:

(or (simple-function args)
    (complex-function args)
    (too-lazy-to-optimize-this-function args))
Haircloth answered 18/11, 2011 at 23:50 Comment(2)
I'm accepting this one because it discusses gotcha that I was unaware of.Scarlettscarp
It's not a gotcha, rather you have to understand everything is eager except lazy sequences, and the functions that work on them. The 'or' short-circuits since it's a macro, it expands to bit.ly/u8xnms . If it were a function, it would evaluate its arguments. Instead, it macroexpands to an if, which is a special form and short-circuits.Cymene
H
11

The other answers all good, but when in doubt, you can always just test it out on the REPL:

user=> (or true (do (println "hello") true))
true
user=> (or false (do (println "hello") true))
hello
true
Hissing answered 19/11, 2011 at 2:5 Comment(0)
Y
4

When in doubt, consult the documentation:

or
macro
Usage:

   (or)  
   (or x)  
   (or x & next)  

Evaluates exprs one at a time, from left to right. If a form returns a logical true value, or returns that value and doesn't evaluate any of the other expressions, otherwise it returns the value of the last expression. (or) returns nil.

(Emphasis mine.)

The documentation for and shows it behaves in the equivalent way too.

Yardstick answered 18/11, 2011 at 23:32 Comment(0)
S
1

As soon as I finished typing this question, I realized I could just look at the documentation for 'or'.

From the docs: "Evaluates exprs one at a time, from left to right. If a form returns a logical true value, or returns that value and doesn't evaluate any of the other expressions, otherwise it returns the value of the last expression. (or) returns nil."

Scarlettscarp answered 18/11, 2011 at 23:30 Comment(0)
E
1

Yes, Clojure does indeed have short circuit evaluation.

One interesting feature in Clojure / other Lisps is that it is also possible to extend the language with new constructs that also provide short-circuit evaluation. This can't be done using functions in most other languages since all the parameters to a function must be evaluated before the function is called.

Here's an example of a macro to implement a short-circuiting NAND function in Clojure:

(defmacro nand 
  ([x] 
    `(not ~x))              ; NAND is equivalent to NOT for one argument
  ([x & xs] 
    `(let [nand# (not ~x)]
       (if nand# 
         true               ; short circuit if we can prove the nand is true
         (nand ~@xs)))))    ; continue with the other expressions otherwise

(nand true true)
=> false

(nand false (println "Expression with a side effect!"))
=> true
Erhard answered 21/11, 2011 at 2:44 Comment(0)
A
0
if (foo() || bar() || foobar()) { /* do stuff */ }

to

(if (or (foo) (bar) (boobar)) (comment do stuff))

or

(when (or (foo) (bar) (boobar)) (comment do stuff))
Ardenia answered 18/11, 2011 at 23:50 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.