Everybody says that pattern matching is a great feature in functional languages. Why?
Can't I simple use ifs and switch cases for everything?
I'd like to understand the advantages of using pattern matching instead of regular procedural programming ifs and switch cases
I'd first like to note that you don't use pattern matching "instead" of switch statements. Scala doesn't have switch
statements, what it does have is match blocks, with cases inside that superficially look very similar to a switch statement.
Match blocks with pattern matching does everything that switch
does, and much more.
A) It's not restricted to just primitives and other types that Oracle have chosen to "bless" in the language spec (Strings and Enums). If you want to match on your own types, go right ahead!
B) Pattern matching can also extract. For example, with a tuple:
val tup = ("hello world", 42)
tup match {
case (s,i) =>
println("the string was " + s)
println("the number was " + i
}
With a list:
val xs = List(1,2,3,4,5,6)
xs match {
case h :: t =>
// h is the head: 1
// t is the tail: 2,3,4,5,6
// The :: above is also an example of matching with an INFIX TYPE
}
With a case class
case class Person(name: String, age: Int)
val p = Person("John Doe", 42)
p match {
case Person(name, 42) =>
//only extracting the name here, the match would fail if the age wasn't 42
println(name)
}
C) pattern matching can be used in value assignment and for-comprehensions, not just in match blocks:
val tup = (19,73)
val (a,b) = tup
for((a,b) <- Some(tup)) yield a+b // Some(92)
D) match blocks are expressions, not statements
This means that they evaluate to the body of whichever case was matched, instead of acting entirely through side-effects. This is crucial for functional programming!
val result = tup match { case (a,b) => a + b }
pattern matching is not somehow an alternative of switch statement, I consider it to be another way of doing dynamic dispatch in oop. They try to do the same thing: calling a different version of the function based on the dynamic type of the arguments
Somehow my edit/addition to @KevinWright answer got thrown away, so I'll add it here as one more nice pattern matching feature...
F) Compiler exhaustiveness check of cases.
If there exists a value matching against which will not be covered by existing cases compiler will warn you about it. This is a very nice feature of the language because if you don't ignore these compiler warnings you won't catch such runtime exceptions or come across a case you didn't think of. If you still run the application and ignore the warning you will get a nice descriptive exception if your value does not match any cases. Here is an illustration:
scala> def badMatch(l: List[Int]): Unit = l match { case x :: xs => println(x) }
<console>:7: warning: match may not be exhaustive.
It would fail on the following input: Nil
def badMatch(l: List[Int]): Unit = l match { case x :: xs => println(x) }
^
badMatch: (l: List[Int])Unit
scala> badMatch(List(1, 2))
1
scala> badMatch(Nil)
scala.MatchError: List() (of class scala.collection.immutable.Nil$)
I prefer to get an exception in this case because it will fail loud and clear, and usually early instead of executing unexpected branches of logic.
If you use if
you would have to use else
, and if you use Java switch
you would have to have default
case to cover all cases. But notice the difference: Scala compiler knows that your empty list is different from non-empty list in this case, or in more broad sense you define the granularity of matches. You could match lists with 1 or 2 elements and ignore the rest, or use any other much more complex patterns without having to worry if you managed to cover all cases.
In short as you use complex extraction and matching logic compiler will make sure you didn't miss any cases. There is nothing similar in Java unless you use a default case like default
or else
.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With