Ryan
Ryan

Reputation: 10101

Scala implicit return and if-then-else statement

One of my issue is - if we rely on implicit return, it is error prone,

eg.

def foo(bar: Int): Int =
{
    if (f1(bar)) 0
    if (f2(bar)) 1 
    else -1
}

Sometimes we just forgot the else statement, in order to fix this issue, curly brace is enforced like, .e.g

def foo(bar: Int): Int =
{
    if (f1(bar)) {
        0
    } else if (f2(bar)) {
        1
    } else {
        -1
    }
}

But the new style is too verbose IMHO, any way to fix?

Upvotes: 2

Views: 6165

Answers (5)

0__
0__

Reputation: 67280

Maybe the following guide lines help:

(1) An if statement without else branch must have a side-effect (because the result type of the expression is Unit); therefore to avoid the mistake above, always use a curly brace and the body on a new line, or enforce an empty line below that statement. Instead of...

if (f1(bar)) addToParent()  // addToParent has side-effect
if (f2(bar)) 1
else -1

...use either...

if (f1(bar)) addToParent()

if (f2(bar)) 1
else -1

...or...

if (f1(bar)) {
  addToParent()
}
if (f2(bar)) 1
else -1

The result is, you should not see anywhere two if keywords aligned directly below each other. In other words, when you see two ifs above each other, you must have made a mistake.

(2) If you have multiple checks, use explicit braces, or if they are rather short (like in your example), keep them on one line:

if (f1(bar)) 0 else if (f2(bar)) 1 else -1

because this wouldn't compile:

if (f1(bar)) 0 if (f2(bar)) 1 else -1

<console>:3: error: ';' expected but 'if' found.
              if (f1(bar)) 0 if (f2(bar)) 1 else -1
                             ^

If Scala was a pure functional language or could identify side-effects, the compiler could warn you of course. Also an IDE could help you here. I tried this in IntelliJ IDEA (as it has quite a few smart warnings), but it does not give a warning. I think it's a good case where the presentation compiler could spot immediately branches which are odd because they are either unused literals (as in your case), or calls to methods which appear to have no side-effect (return type is not Unit and according to style guide method does not have empty parentheses).

Upvotes: 0

Rex Kerr
Rex Kerr

Reputation: 167871

As a practical matter, I never have this problem, and I make loads of silly mistakes. I suspect that you are entirely capable of learning to not skip the else statement. (Note that braces don't help anything; you can still skip the else in the middle one.)

If you really, really find this a problem, you can either abuse the match statement:

true match {
  case _ if f1(bar) => 0
  case _ if f2(bar) => 1
  case _ => -1
}

which will prevent you from making any mistakes. Or you can write yourself a utility method:

trait Conditional[+A] {
  def apply[B >: A](p: => Boolean, b: => B): Conditional[B]
  def or[B >: A](default: => B): B
}
class FoundIt[+A](it: A) extends Conditional[A] {
  def apply[B >: A](p: => Boolean, b: => B) = this
  def or[B >: A](default: => B) = it
}
class NothingYet[+A] extends Conditional[A] {
  def apply[B >: A](p: => Boolean, b: => B) = {
    if (p) new FoundIt(b) else this
  }
  def or[B >: A](default: => B) = default
}
def ifelse[A](p: => Boolean, a: => A) = (new NothingYet[A]).apply(p,a)

ifelse( f1(bar), 0 )( f2(bar), 1 ) or -1 

This is a little messy for long expressions, so you could also (use :paste to stick this in the REPL in one big block if you want it to work there):

trait Predicate[+A] {
  def Or(p: => Boolean): Loadable[A]
  def Else[B >: A](default: => B): B
} 
trait Loadable[+A] {
  def Then[B >: A](b: => B): Predicate[B]
}
object NotYetTrue extends Predicate[Nothing] {
  def Or(p: => Boolean) = if (p) ActuallyLoad else SkipLoading
  def Else[B >: Nothing](default: => B) = default
}
object SkipLoading extends Loadable[Nothing] {
  def Then[B >: Nothing](b: => B) = NotYetTrue
}
object ActuallyLoad extends Loadable[Nothing] {
  def Then[B >: Nothing](b: => B) = new Loaded[B](b)
}
class Loaded[+A](a: A) extends Predicate[A] with Loadable[A] {
  def Or(p: => Boolean) = this
  def Else[B >: A](default: => B) = a
  def Then[B >: A](b: => B) = this
}
def If(p: => Boolean): Loadable[Nothing] = NotYetTrue.Or(p)

Now the only trick is that you have to use :paste in the REPL to write a multi-line statement, and put the continuation on the end of the previous line:

If (f1(bar)) Then 0 Or
(f2(bar)) Then 1 Else
-1

You can also use Then { 0 on one line and start up again with } Or on the next, or write everything with parens/braces and dots (which is REPL-friendly):

If (f1(bar)) .Then (0) .
Or (f2(bar)) .Then (1) .
Else (-1)

Anyway, all this trickery is a good illustration of how to build sophisticated DSLs with Scala, but not really the best way to solve your problem. You should just learn to be a little more careful with if/else statements; otherwise, people are going to be puzzled as to why you're not doing things the standard way. (And if it's performance-critical, it may be slow--match is pretty good, but the If wizardry is not good in a high-performance loop.)

Upvotes: 9

huynhjl
huynhjl

Reputation: 41646

If you had many statements, then the braces would be needed. In this particular example, you have a single expression, you can remove the braces altogether and it would make the error more apparent and it would be less verbose. Also if you use an IDE, you can reformat the code, it helps point out issues.

Here is an example of formatting without any braces.

  • foo is the correct version.
  • foo2 is your sometimes we just forgot something. See the syntax error.
  • foo3 has also the else forgotten but because the code is in a brace block, the compiler consider the if (f1(bar)) 0 statement as a valid isolated statement.

enter image description here

If I had reformatted the code before the screenshot, it would be more obvious in foo2 that the if/else were mismatched.

Upvotes: 0

som-snytt
som-snytt

Reputation: 39577

I get you. Not enough people say, More than one conditional is too much for one function!

Fold over some functions to apply. Also expressible as collectFirst.

object Ifless extends App {
  def f1(i: Int) = i == 1
  def f2(i: Int) = i == 2
  def p1(i: Int) = if (f1(i)) Some(0) else None
  def p2(i: Int) = if (f2(i)) Some(1) else None
  def default(i: Int) = Some(-1)  //whatever
  def f(i: Int): Int = {
    val ps = List(p1 _, p2 _, default _)
    ps.foldLeft(None: Option[Int])((r,v) => r match {
      case None => v(i)
      case x => x
    }) getOrElse -1  //whatever
  }
  println(f(1))
  println(f(2))
  println(f(3))
}

i.e.,

object Ifless2 extends App {
  // imagine this is not a switchable match
  def fs: List[PartialFunction[Int, Int]] = List (
    { case 1 => 0 },
    { case 2 => 1 },
    { case _ => -1 }
  )
  def f(i: Int): Int = {
    fs.collectFirst { case pf if pf.isDefinedAt(i) => pf(i) } getOrElse -1
  }
  println(f(1))
  println(f(2))
  println(f(3))
}

Also, it's too bad scalac doesn't emit something like the infamously helpful, "warning: a pure expression does nothing in statement position".

Upvotes: 1

dhg
dhg

Reputation: 52681

It's unclear what you are asking. If you want scala to know whether you mean if or else if when you type the wrong thing... well you're probably out of luck. You've got to type the right commands, and adding or removing braces won't change whether you get compile-time errors or not.

If you just want to know how to write if-elseif-else without parentheses, then:

if (f1(bar)) 0
else if (f2(bar)) 1 
else -1

Upvotes: 0

Related Questions