Type of if expressions with no "else"

Hi!

I have a question about if expressions that have no else clause. It’s generally inappropriate to access the value of such an expression, but beginners make mistakes and the symptoms (incl. consequent error messages) matter.

Here’s some toy code in Scala 2:

val x = 123
val foo = if (x > 0) 1                    // type of foo is AnyVal
val bar = if (x > 0) 1 else if (x < 0) 2  // type of bar is AnyVal

And the same in Scala 3:

val x = 123
val foo = if x > 0 then 1                       // type of foo is Unit(!)
val bar = if x > 0 then 1 else if x < 0 then 2  // type of bar is AnyVal

I love Scala 3, but the way Scala 2 treats these expressions seems simpler to explain. Moreover, such code is generally written in error, and Scala 2 produces more consistent symptoms.

I wonder if the change in Scala 3 is intentional and permanent?

The actual behavior is the same in both.
What happens is that conceptually an else () is added.
However, in Scala 3 they also apply the value discard rule, so the then branch becomes { ..; () }

This makes total sense, an if without else is just for performing side-effects; thus, should not be assigned to a value.

2 Likes

Assigning a Unit-typed if-expression to a val or val is probably a bug. Perhaps this should be a case where the compiler emits a warning. Or it could be a rule in Scalafix or Scala Wartremover (I quickly searched for missing else but could not find any such rule).

3 Likes

Assigning a Unit-typed if-expression to a val or val is probably a bug. Perhaps this should be a case where the compiler emits a warning.

It’s definitely a bug in a well-ordered world.

For dealing with The Good, The Bad, and The Ugly in my work-day code ( … try blocks, nulls…) - an if statement that’s kicking off some side effect has to return a “there’s nothing to see here” value to stay part of The Good to fit in contexts of IO or Either. I deal with having to distinguish between Unit and AnyVal by declaring it to be Unit.

Do you think the presence of “there’s nothing to see here” values like Unit, or AnyVal, (or Any, or Nothing, or Object) indicates the bug better than “if with no else”? Unit seems to be OK at the tail-end of side effects; in my code the rest of them are signs of trouble.

Yielding () can definitely be valuable (pun intended :slight_smile: ) and in Scala :scala: there is this explicit empty value () or “:zero: -element-tuple” (instead of special-casing void or similar) and that’s nice to be able to talk about nothing as a value. So that’s why I proposed a warning rather than an error, when assigning a potentially empty if.

I agree that a good warning message would be nice.

Actually, we already get a warning for such if expressions because of the discarded value. Unfortunately, the warning points at the then branch and says A pure expression does nothing in statement position; you may be omitting necessary parentheses, which can be unhelpful or even misleading in practice.

Incidentally, the issue isn’t constrained to vals and vars. Here’s a def:

 def foo(x: Int) =
   if x != 0 then
     100 / x
   if x == 0 then
     0

Code like that is problematic in more than one way, but it’s nevertheless code that neophytes do write. A good warning message would help.

2 Likes