tags:

views:

138

answers:

5

If I have something like this:

int foo() {
    if(somecondition) {
       // some code
       if(cond2) return -1;
       // some code again
    }
    if(cond3){
       // something again
    }
    return bar();
 }

How do I express it in Clojure?

it should not be written as if_elseif because both the somecondition and cond3 may be true and both may be execute.

From the suggestions below, I am suggesting one more solution. Please let me know if that is right or not:

(defn foo []
    (if (somecondition)
        ( (some-code) (if (cond2) -1 (some-code-again)) )
        1 ; empty statement
    )
    (if (cond3) (something-again) 1)
    (bar))
+3  A: 

You could argue that this is bad style since it's hard to understand the flow of control unless of course this pattern is being used to return error conditions (which is common in C). Clojure supports exceptions. If you really want to send control elsewhere use them.

dnolen
Interesting idea on errors, +1! Of course some people use exceptions for control flow, but I *really* really dis-recommend that!
Carl Smotricz
+1  A: 

I think it would look something like this:

(defn foo
  []
  (if somecondition
    (do
      ; some code
      (if cond2
        -1
        (do
          ; somecode again
          (if cond3
            (do
              ; something again))
          (bar))))
    (do
      (if cond3
        (do
          ; something again))
      (bar))))

How ugly - don't do that :)

As far as I know the lack of control jumps is by design. This function is entirely side-effect driven which is a red flag that maybe the underlying problem may be better represented in another way, but it is hard to give any real advice as the example is entirely abstract. CL has a return-from which is rarely used "because all Lisp expressions, including control constructs such as loops and conditionals, evaluate to a value". There is no return-from in Clojure.

Timothy Pratley
see my example for a much more succinct way to do what he's describing.
Jeremy Wall
except it doesn't do exactly the same thing...
Timothy Pratley
+1  A: 

Lets start off with some literal translations to establish a common ground:

int foo() {
(defn foo []

    if(somecondition) {
    (if somecondition 

       // some code
       (some-code 1 2 3)

       if(cond2) return -1;
       (if cond2 
          -1
          // some code again
          (some-code 1 2 3)

        }
              if(cond3){
              // something again
              }
              (if cond3
                  (something :again)

                  return bar();
                  (bar)))))
 }

We have to adjust this to make it what could be described at "all one big long return statement" or as people that really dig functional programming call it "a function".

(defn helper-function []
   (if cond3
      (something again))
   bar))

(defn foo []
    (if somecondition 
      (some-code 1 2 3)
      (if cond2 
          -1
          (helper-function)
     (helper-function)

The function has two places where the return point is determined. in clojure this is just the ends of the function. A clojure function returns the result of the last expression evaluated before the function is over otherwise we would be writing return everywhere. (helper-function) had to be called twice because there are two code paths that use it.

Arthur Ulfeldt
That we arrived at similar solutions could be coincidence but I'd say it points to Clojure's (and Python's) philosophy that there should be one obvious best way to do most things. So far so good. But I have a parenthetical question (in the next comment).
Carl Smotricz
We define helper-function outside (and more importantly, before) (somecondition) and "some code". Is there still a loophole for malfunction because we could be missing out on side effects or closures from the preceding code?
Carl Smotricz
Not that I see, though anyone can write code with no bugs they can see :)
Arthur Ulfeldt
@Carl, why did you call helper function 2 times? In my C code there are 2 paths that can call helper function, but can we not avoid the call because of this rule:(defn foo [] (foo1) (foo2) ) In this case we are sure that (foo2) always gets called. So, I think in your code, if you move the last call to helper-function outside your if-block, then it will automatically always get called.
ajay
@Arthur: Good nuff for me. Thanks!
Carl Smotricz
@Ajay: The above is Arthur's code, not mine. But no matter, it's very similar. It's not really being called twice; those parentheses are in two mutually exclusive conditionals. If the function is to have a "return -1" in the middle, it can't be rewritten to "fall" into the final helper function call. Please see my own answer in about 10 minutes for more information!
Carl Smotricz
+3  A: 

You can undo the knots in the flow of control by refactoring some of the code out into a separate function; this will work in C and in Clojure too.

Here's my stab at it. Hand translated and not tested, so it could be buggy.

(defn foo []
  (let [foo2 (fn [] 
    (if cond3 "something again")
    (bar))]
    (if somecondition
      (do
        "some code"
        (if cond2
          -1
          (do
            "some code again"
            (foo2))))
      (foo2))))


UPDATE with some explanation.

Because ajay asked, I'd like to expose a little of the thinking that led me to the above solution.

; shortcuts

c1 = somecondition
c2 = cond2
c3 = cond3

; truth table (this helped me a bit with my thinking but didn't go directly into the design)

c1 c2 c3 results
----------------
T  T  .  "some code" -1
T  F  T  "some code" "some code again" "something again" bar()
T  F  F  "some code" "some code again" bar()
F  .  T  "something again" bar()
F  .  F  bar()

The way Clojure works, there's no "return". A Clojure function always returns the last expression to be evaluated. If we want to produce the same side effects (from "some code", "some code again" and "something again") and result as the C/Java code, we need to make the code run in such a way that the result is really the last thing executed.

The only way to be able to return -1 is to rework the code so there is a branch from the start that ends - really ends - at "-1". That means that the following code must be called from within one of the other IF branches. In fact, it appears in two branches. So as not to have to repeat the code, I pulled it into a function of its own.

Here's the translation of my code to pseudocode:

function foo2:

if c3 then
   "something again"
endif
bar()

function foo:

if c1 then
   "some code"
   if c2 then
     -1            <-- end 1
   else
     "some code again"
     foo2          <-- end 2
   endif
else
   foo2            <-- end 3
endif

end 1 is your tricky "return -1". end 2 includes "some code again", end 3 doesn't. Both end 2 and end 3 test c3, maybe do "something again" and then return bar().

Carl Smotricz
Ah, I'm happy to see my version looks a lot like Arthur's. I used a "let fn" (no letfn because I don't have the syntax for that memorized) so as to keep the helper function local to where it's used.
Carl Smotricz
the when macro is a much more readable way to do this. Since the posters code has no real else blocks the if built-in is cond of overkill.
Jeremy Wall
I believe you overlooked the `return -1`, which drastically changes the flow of execution. As you can see in my pseudocode above, my translation requires two `if`s with `else` branches. I don't see any opportunity to use `when` to good effect.
Carl Smotricz
A: 

That code example is ready made for when

(defn foo []
  (when somecondition
    ; code goes here
    (do-somecondition-code)
    (when cond2
       ; more code goes here
       (do-cond2-code)))
  (when cond3
      ; even more code here
      (do-cond3-code))
  (bar))

The code is clear readable and concise and does exactly what you specified.

Jeremy Wall
I disagree. Nowhere in your code do I see the equivalent of `return -1`. You failed to capture the most essential part of the question's logic.
Carl Smotricz
You are completely correct. I missed the return -1 part of his example. I withdraw my example.
Jeremy Wall