buddingprogrammer
buddingprogrammer

Reputation: 151

Clojure: Multiple tasks in if

As I've come to know it, the form of an if is (if [condition] [true] [false]). Similarly, cond is (cond [condition] [true] ... [condition] [true] [false]). Each true and false segment seems to only accept one action. If I want to represent the following logic:

if (i > 0)
{
    a += 5;
    b += 10;
}

I think I have to do:

(if (> i 0) (def a (+ a 5)))
(if (> i 0) (def b (+ b 10)))

Just so the second action isn't confused as a false result. Is this how it needs to be, or is there a way to create a larger body for an if?

p.s. I also suspect redefining a and b each time isn't the best way to increment, but also haven't seen a different way of doing that. I've had to also redefine lists when using conj.

Upvotes: 4

Views: 2047

Answers (3)

Thumbnail
Thumbnail

Reputation: 13483

You are looking through the wrong end of the telescope. Bear in mind that

  • Clojure has no local variables.
  • Clojure has no actions (usually called statements), only expressions returning a value.

There is no Clojure equivalent of the statement a += 5;.

However, expressions can have side effects: print and the like accomplish nothing else. The do form allows you to accomplish a series of side effects before returning a final value. For example,

(do (print a) (print b) (+ a b))
  • prints a,
  • prints b,
  • returns their sum.

That's why, as you write of the if form ...

Each true and false segment seems to only accept one action.

What Clojure does have is

  • local name bindings, with the let form and
  • a derived version of let called loop that implements a primitive form of recursion that can replace simple uses of loops in languages like C or Java.

Between them, let and its offspring loop allow you to write most simple control structures. to determine if this applies to your program fragment ...

if (i > 0)
{
    a += 5;
    b += 10;
}

... we'd have to see the context.

However, here's a greatest common divisor function in C (untested)

long gcd (long i, long j)
{
  long m = i, n = j;
  while (n != 0)
  {
    long t = n;
    n = m % n;
    m = t;
  }
}

and in Clojure

(defn gcd [i j]
  (loop [m i, n j]
    (if (zero? n)
      m
      (recur n (mod m n)))))

Both of these can be abbreviated.

Upvotes: 5

Piotrek Bzdyl
Piotrek Bzdyl

Reputation: 13185

The other answer covered the explicit question about having more than one expression in the if branch (using do or by using when if there is no else branch as when wraps its nested expressions implicit do).

However, there is another issue in the question with using state which is usually local to the function. I don't think an atom stored in a global var is the best way to handle that, and as Clojure programs tend to minimise global state it's usually better to keep the state local.

We use let to define the local state and narrow its scope (NB it also supports destructuring):

(let [i 0
      a 5
      b 10]
  (println i)
  (println a)
  (println b))

let assigns a value to a local variable and it cannot be redefined. If we need to update local state we can use recursion by calling recur directly on the function or by using loop and recur.

For example:

(defn plus [a b]
  (if (> b 0)
    (do
      (println "Recurring...")
      (recur (inc a) (dec b)))
    (do
      (println "Returning result")
      a)))

Or:

(defn plus [a b]
  (loop [a a
         b b]
    (if (> b 0)
      (do
        (println "Recurring...")
        (recur (inc a) (dec b)))
      (do
        (println "Returning result")
        a))))

Upvotes: 1

gfredericks
gfredericks

Reputation: 1332

The most direct transaction, using atoms instead of vars (def), would be

;; assuming something like (def a (atom 0)) (def b (atom 0))
(if (> i 0)
  (do
    (swap! a + 5)
    (swap! b + 10)))

or

(when (> i 0)
  (swap! a + 5)
  (swap! b + 10))

Upvotes: 7

Related Questions