Reputation: 10101
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
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 if
s 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
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
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.If I had reformatted the code before the screenshot, it would be more obvious in foo2
that the if/else were mismatched.
Upvotes: 0
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
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