`ifelse` query

Hello,
I have the following code

function test(x,y)
   ifelse(x>y,return x, throw(error("err")))
end

For me the function is always returning error. This does not make sense to me as for test(2,1) it should return 2.

This is only happening when error is thrown. Otherwise for numerical outputs there is no problem.

Compare how your example is parsed with wrapping the return in parentheses.

julia> :(function test(x,y)
          ifelse(x>y,return x, throw(error("err")))
       end)
:(function test(x, y)
      #= REPL[9]:1 =#
      #= REPL[9]:2 =#
      ifelse(x > y, return (x, throw(error("err"))))
  end)

julia> :(function test(x,y)
          ifelse(x>y,(return x), throw(error("err")))
       end)
:(function test(x, y)
      #= REPL[8]:1 =#
      #= REPL[8]:2 =#
      ifelse(x > y, return x, throw(error("err")))
  end)
1 Like

Ah I see. I wasn’t aware of the parsing.

Even though this function is similar to the if statement syntax, you probably don’t want a return statement within a function call.

2 Likes

Just to add to the explanation of @jakobjpeters about the return statement:
Another issue is the use of ifelse itself.

From the docstring (EDIT: I removed the irrelevant parts of the docstring to reduce noise):

  ifelse(condition::Bool, x, y)

  [...] This differs from ? or if in that it is an ordinary function, so all the arguments are evaluated
  first. [...]

So the error will always be thrown, no matter what:

julia> ifelse(true, 1, error("2"))
ERROR: 2
Stacktrace:
 [1] error(s::String)
   @ Base ./error.jl:35
 [2] top-level scope
   @ REPL[17]:1

4 Likes

Yes indeed. I figured that out as well. But then the question becomes is there a safe way to throw error statements?

What do you mean by safe?

You can just use a regular if statement:

function test(x,y)
    if x>y
        return x
    else
        error("err")
    end
end

(Note that throw(error("...")) doesn’t really work, because throw and error do the same thing, except that throw takes a specific instance of an Exception, whereas error just takes some string. There will be no difference whether you leave the outer throw() away or not, because the inner error will already have thrown an exception.)

or if it should be shorter (but arguably less readable)

function test(x,y)
    return x>y ? x : error("err")
end

(Note that x > y ? return x : error("err") doesn’t work again because of the parsing – It would work as x > y ? (return x) : error("err") though.)

2 Likes

I see your point. Thanks!!

1 Like

Just to add one more possibility…

I personally like to use short-circuiting for this kind of check. Stylistically, it feels more like an aside (“just to be sure…”), rather than part of the program logic.

In this case

function test(x,y)
    x > y || error("err")
    return x
end

Or (again just personal preference) because I like testing affirmatively for the pathological case, x <= y && error("err") or !(x > y) &&...

2 Likes

ifelse behaves the same as myIfelse(cond, trueval, falseval) = (cond ? trueval : falseval). The main difference is that there is extra machinery to encourage the compiler to emit conditional move (e.g. CMOV) instructions. This is intended for settings where you either want to get SIMD or where you know that cond is hard to predict, i.e. where you have a pretty precise picture of the native code and uarch-state you want.

So, since ifelse is a function, the caller must evaluate the arguments before the call, and the side-effects (like returning or throwing an exception) happen before the ifelse even runs.
Your expected behavior could be achieved by

julia> macro macro_ifelse(condExpr, trueExpr, falseExpr)  :($condExpr ? $trueExpr : $falseExpr) end
julia> @macro_ifelse(1<2, 1, throw("oops"))
1

But that would be a mere shorthand for a ternary / if-else:

julia> Base.@macroexpand @macro_ifelse(1<2, 1, throw("oops"))
:(if 1 < 2
      1
  else
      Main.throw("oops")
  end)

What this also tells you is that there is no difference between c ? a : b and if c a else b end: Both are parsed into the same expression and are just as equivalent as 1000 and 1_000.

2 Likes