Pattern matching vs if-else

Scala

Scala Problem Overview


I'm novice in Scala. Recently I was writing a hobby app and caught myself trying to use pattern matching instead of if-else in many cases.

user.password == enteredPassword match {
  case true => println("User is authenticated")
  case false => println("Entered password is invalid")
}

instead of

if(user.password == enteredPassword)
  println("User is authenticated")
else
  println("Entered password is invalid")

Are these approaches equal? Is one of them more preferrable than another for some reason?

Scala Solutions


Solution 1 - Scala

class MatchVsIf {
  def i(b: Boolean) = if (b) 5 else 4
  def m(b: Boolean) = b match { case true => 5; case false => 4 }
}

I'm not sure why you'd want to use the longer and clunkier second version.

scala> :javap -cp MatchVsIf
Compiled from "<console>"
public class MatchVsIf extends java.lang.Object implements scala.ScalaObject{
public int i(boolean);
  Code:
   0:	iload_1
   1:	ifeq	8
   4:	iconst_5
   5:	goto	9
   8:	iconst_4
   9:	ireturn

public int m(boolean);
  Code:
   0:	iload_1
   1:	istore_2
   2:	iload_2
   3:	iconst_1
   4:	if_icmpne	11
   7:	iconst_5
   8:	goto	17
   11:	iload_2
   12:	iconst_0
   13:	if_icmpne	18
   16:	iconst_4
   17:	ireturn
   18:	new	#14; //class scala/MatchError
   21:	dup
   22:	iload_2
   23:	invokestatic	#20; //Method scala/runtime/BoxesRunTime.boxToBoolean:(Z)Ljava/lang/Boolean;
   26:	invokespecial	#24; //Method scala/MatchError."<init>":(Ljava/lang/Object;)V
   29:	athrow

And that's a lot more bytecode for the match also. It's fairly efficient even so (there's no boxing unless the match throws an error, which can't happen here), but for compactness and performance one should favor if/else. If the clarity of your code is greatly improved by using match, however, go ahead (except in those rare cases where you know performance is critical, and then you might want to compare the difference).

Solution 2 - Scala

Don't pattern match on a single boolean; use an if-else.

Incidentally, the code is better written without duplicating println.

println(
  if(user.password == enteredPassword) 
    "User is authenticated"
  else 
    "Entered password is invalid"
)

Solution 3 - Scala

One arguably better way would be to pattern match on the string directly, not on the result of the comparison, as it avoids "boolean blindness". http://existentialtype.wordpress.com/2011/03/15/boolean-blindness/

One downside is the need to use backquotes to protect the enteredPassword variable from being shadowed.

Basically, you should tend to avoid dealing with booleans as much as possible, as they don't convey any information at the type level.

user.password match {
    case `enteredPassword` => Right(user)
    case _ => Left("passwords don't match")
}
    

Solution 4 - Scala

Both statements are equivalent in terms of code semantics. But it might be possible that the compiler creates more complicated (and thus inefficient) code in one case (the match).

Pattern matching is usually used to break apart more complicated constructs, like polymorphic expressions or deconstructing (unapplying) objects into their components. I would not advice to use it as a surrogate for a simple if-else statement - there's nothing wrong with if-else.

Note that you can use it as an expression in Scala. Thus you can write

val foo = if(bar.isEmpty) foobar else bar.foo

I apologize for the stupid example.

Solution 5 - Scala

It's 2020, the Scala compiler generates far more efficient bytecode in the pattern matching case. The performance comments in the accepted answer are misleading in 2020.

The pattern match generated byte code gives a tough competition to if-else at times pattern matching wins giving much better and consistent results.

One can use pattern match or if-else based on the situation & simplicity. But the pattern matching has poor performance conclusion is no longer valid.

You can try the following snippet and see the results:

def testMatch(password: String, enteredPassword: String) = {
    val entering = System.nanoTime()
    password == enteredPassword match {
      case true => {
        println(s"User is authenticated. Time taken to evaluate True in match : ${System.nanoTime() - entering}"
        )
      }
      case false => {
        println(s"Entered password is invalid. Time taken to evaluate false in match : ${System.nanoTime() - entering}"
        )
      }
    }
  }


 testMatch("abc", "abc")
 testMatch("abc", "def")
    
Pattern Match Results : 
User is authenticated. Time taken to evaluate True in match : 1798
Entered password is invalid. Time taken to evaluate false in match : 3878


If else :

def testIf(password: String, enteredPassword: String) = {
    val entering = System.nanoTime()
    if (password == enteredPassword) {
      println(
        s"User is authenticated. Time taken to evaluate if : ${System.nanoTime() - entering}"
      )
    } else {
      println(
        s"Entered password is invalid.Time taken to evaluate else ${System.nanoTime() - entering}"
      )
    }
  }

testIf("abc", "abc")
testIf("abc", "def")

If-else time results:
User is authenticated. Time taken to evaluate if : 65062652
Entered password is invalid.Time taken to evaluate else : 1809

PS: Since the numbers are at nano precision the results may not accurately match to the exact numbers but the argument on performance holds good.

Solution 6 - Scala

For the large majority of code that isn't performance-sensitive, there are a lot of great reasons why you'd want to use pattern matching over if/else:

  • it enforces a common return value and type for each of your branches
  • in languages with exhaustiveness checks (like Scala), it forces you to explicitly consider all cases (and noop the ones you don't need)
  • it prevents early returns, which become harder to reason if they cascade, grow in number, or the branches grow longer than the height of your screen (at which point they become invisible). Having an extra level of indentation will warn you you're inside a scope.
  • it can help you identify logic to pull out. In this case the code could have been rewritten and made more DRY, debuggable, and testable like this:

val errorMessage = user.password == enteredPassword match {
  case true => "User is authenticated"
  case false => "Entered password is invalid"
}

println(errorMesssage)

Here's an equivalent if/else block implementation:

var errorMessage = ""

if(user.password == enteredPassword)
  errorMessage = "User is authenticated"
else
  errorMessage = "Entered password is invalid"

println(errorMessage)

Yes, you can argue that for something as simple as a boolean check you can use an if-expression. But that's not relevant here and doesn't scale well to conditions with more than 2 branches.

If your higher concern is maintainability or readability, pattern matching is awesome and you should use it for even minor things!

Solution 7 - Scala

I'v came across same question, and had written tests:

     def factorial(x: Int): Int = {
        def loop(acc: Int, c: Int): Int = {
          c match {
            case 0 => acc
            case _ => loop(acc * c, c - 1)
          }
        }
        loop(1, x)
      }
    
      def factorialIf(x: Int): Int = {
        def loop(acc: Int, c: Int): Int = 
            if (c == 0) acc else loop(acc * c, c - 1)
        loop(1, x)
      }
    
    def measure(e: (Int) => Int, arg:Int, numIters: Int): Long = {
        def loop(max: Int): Unit = {
          if (max == 0)
            return
          else {
            val x = e(arg)
            loop(max-1)
          }
        }
    
        val startMatch = System.currentTimeMillis()
        loop(numIters)
        System.currentTimeMillis() - startMatch
      }                  
val timeIf = measure(factorialIf, 1000,1000000)
val timeMatch = measure(factorial, 1000,1000000)

                         

timeIf : Long = 22 timeMatch : Long = 1092

Solution 8 - Scala

I am here to offer a different opinion: For the specific example you offer, the second one (if...else...) style is actually better because it is much easier to read.

In fact, if you put your first example into IntelliJ, it will suggest you to change to the second (if...else...) style. Here is the IntelliJ style suggestion:

Trivial match can be simplified less... (⌘F1) 

Suggests to replace trivial pattern match on a boolean expression with a conditional statement.
Before:
    bool match {
      case true => ???
      case false => ???
    }
After:
    if (bool) {
      ???
    } else {
      ???
    }

Solution 9 - Scala

In my environment (scala 2.12 and java 8) I get different results. Match performs consistently better in the code above:

timeIf: Long = 249 timeMatch: Long = 68

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionSotericView Question on Stackoverflow
Solution 1 - ScalaRex KerrView Answer on Stackoverflow
Solution 2 - ScalaretronymView Answer on Stackoverflow
Solution 3 - ScalaClément D.View Answer on Stackoverflow
Solution 4 - ScalaziggystarView Answer on Stackoverflow
Solution 5 - ScalaGanaView Answer on Stackoverflow
Solution 6 - ScalaKevin LiView Answer on Stackoverflow
Solution 7 - ScalaAndreyView Answer on Stackoverflow
Solution 8 - ScalaYangView Answer on Stackoverflow
Solution 9 - ScalaFrank EgginkView Answer on Stackoverflow