Pattern matching is a powerful tool in Scala. it has a common syntax like
v match {
case v1 if ??? => ???
case v2 => ???
case _ => ???
}
In this chapter, we will discuss how to use it in different scenarios
How to match literal value?
Let's say we have an int variable v:Int
, we want to implement the following logic
if v==1
then printThis is 1if v==2
then printThis is 2for the others just print I don't care about this number
Usually, we can do it by if-else
if(v==1)
println("This is 1")
else if (v==2)
println("This is 2")
else
println("I don't care about this number")
We also can do it by pattern-matching
v match {
case 1 => println("This is 1")
case 2 => println("This is 2")
case _ => println("I don't care about this number")
}
In this block, we just give the value to each branch directly and use _
to cover the rest of values. _
is a powerful symbol in scala, it has different meanings in different scenarios, we will talk about it detailedly in other chapters. In this block, it stands for any value of any type
.
Pattern matching also support the if-guard
, the previous example can also be implemented like this
v match {
case _ if v == 1 => println("This is 1")
case _ if v == 2 => println("This is 2")
case _ => println("I don't care about this number")
}
The meaning of case _ if v == 1
is if any value of any type equals 1 then do something.
There is also an OR
logic in pattern matching, try the following example
v match {
case 1|2 => println("This is 1 or 2")
case _ => println("I don't care about this number")
}
How to match variable?
This scenario is an edge case, but it's very interesting. Let's say we have a function like this
def dosomething(v:Int,given:Int):Unit ={
.....
}
We want this function to print a log got it
when v==given
, how do we implement it using pattern matching?
Could we do it like this?
def dosomething(v:Int, given:Int):Unit ={
v match {
case given => println("got it")
case _ => println("no idea")
}
}
When we run this code, we will find this function print got it
for any given parameter, Why?
The meaning of case given => println("got it")
here is assign any values of any type to given
then do something.
The compiler treat given
as a new variable which contains the value of v
, not the value of parameter given
. To make compiler do the right thing, we need to use `given` like this
def dosomething(v:Int, given:Int):Unit ={
v match {
case `given` => println("got it")
case _ => println("no idea")
}
}
How to match type?
Sometimes we want to do different thing for different type, for example
If the type is Int, then print This is IntIf the type is String, then print This is StringFor other types, just print no idea
We can implement it using pattern matching like this
def dosomething(v:Any)={
v match {
case _:Int => println("This is Int")
case _:String => println("This is String")
case _ => println("no idea")
}
}
We see case _
again ! the last one has the same meaning as previous examples. for case _:Int=>
, it means to do something for any values of Int type.
It's pretty simple, right? How about changing Any to List[Any]? does this code work well?
def dosomething(v:List[Any])={
v match {
case _:List[Int] => println("This is Int")
case _:List[String] => println("This is String")
case _ => println("no idea")
}
}
Oops, we get a wrong answer!
$ dosomething(List("A","B","C"))
This is Int
Actually, when we compile this code, we will get a warning
Warning:(43, 19) non-variable type argument String in type pattern List[String] (the underlying of List[String]) is unchecked since it is eliminated by erasure
case _: List[String] => "String"
Scala is a JVM language, the Scala code will be translated to Java code. The type parameter in Java Generics will be erased. So for a higher kind type in Scala, we can't match the inner type.
How to match case class?
case class plays an important role in Scala, it gives us a bunch of utility methods and sugar syntax. we will have a look at how it works in pattern matching.
Unlike the common class, we can create a case class instance by class name
case class MyClass(id:Int)
val myClass = MyClass(1)
In pattern matching, we also can get the content of case class by class name
def extractId(v:Any)={
v match {
case MyClass(id) => println(s"The id is ${id}")
case _ => println("No idea")
}
}
In this example, case MyClass(id)=>
will do three things
check if the type of v is MyClassassign the id of MyClass
to variableiddo some logic
We must give the same number of variables as the number of constructor parameters, or we will get a compile error.
We also can apply the type matching and if-guard on the case class matching
case class MyClass(info:Any)
def extractIntInfo(v:MyClass) = {
v match {
case MyClass(id:Int) if id > 0 => println(s"The id is ${id}")
case _ => println("No idea")
}
}
$ extractIntInfo(MyClass(1))
The id is 1
$ extractIntInfo(MyClass(-1))
No idea
$ extractIntInfo(MyClass("hello"))
No idea
Usually, we will use nested case class, how do we extract the information of nested case class? Let's see an example
case class Name(name:String)
case class Age(age:Int)
case class Address(address:String)
case class Person(name:Name,age:Age,address:Address)
def getAddress(p:Person):String = {
???
}
We can implement getAddress
like this
def getAddress(p:Person):String = {
p match {
case Persion(_,_,Address(a)) => println(s"The address is ${a}")
case _ => println("No idea")
}
}
You may notice we use _
for name
and age
value, because we don't care about these two value. for Address
, we just use the non-nested case class matching directly.
What if we want to get Address
and Address.address
together? Try the following example
def getAddress(p:Person):String = {
p match {
case Persion(_,_,v@Address(a)) =>
println(s"The Address is ${v}, Address.address is ${a}")
case _ => println("No idea")
}
}
We can bind the nested pattern to a variable using @
Besides the previous examples, we may get a case class like this
case class MyClass(info:String*)
info:String*
is a variable argument list, its length is not fixed, how should we match it?
Let's try this example
def getInfo(v:MyClass):List[String] = {
v match {
case MyClass(x@_*) => x.toList
case _ => List()
}
}
$ getInfo(MyClass("hello","world"))
res2: List[String] = List("hello", "world")
$ getInfo(MyClass())
res3: List[String] = List()
x@_*
will assign the variable parameter list to x
How to match collection?
In this part, we will talk about two topics: Tuple and List
Tuple
Tuple is simple, we can just treat it as a special case class which has no class name. we can do pattern matching like this
def extractInfo(v:(Any,Any)) = {
v match {
case (k:Int,v:String) => println(s"Key:${k},Value:${v}")
case _ => println("No idea")
}
}
$ extractInfo((1,"ok"))
Key:1,Value:ok
$ extractInfo((1,2))
No idea
$ extractInfo(("hello","world"))
No idea
List
Scala List is very different from the List in Java, it's a recursion data structure, let's see how can we play it with pattern matching.
We can get the head of List
def getHead(l:List[Int]) = {
l match{
case head::tail => println(s"The head is ${head}")
case _ => println("No idea")
}
}
$ getHead(List(1,2,3))
The head is 1
$ getHead(List())
No idea
We also can iterate every element in List
def iterate(l:List[Int]):Unit = {
l match{
case head::tail =>
println(head)
iterate(tail)
case _ =>
println("end")
}
}
$ iterate(List(1,2,3))
1
2
3
end
$ iterate(List())
end
If we know the exact length of List, we also can get all the elements at the same time
def getThreeElements(l:List[Int]) = {
l match {
case List(e1,e2,e3) =>
println(e1)
println(e2)
println(e3)
case _ => println("No idea")
}
}
$ getThreeElements(List(1,2,3))
1
2
3
$ getThreeElements(List(1,2))
No idea
$ getThreeElements(List())
No idea
In these examples, List has two patterns in pattern matching
case head::tail=>Iterate the element of List from left to right
case List(e1,e2,e3) =>Get all the elements of List if the length of List is equal to 3
How to match regex?
Sometimes we need to check if the given string follows some pattern(integer etc.) or extract some information from the given string, let's see how to implement them using pattern matching.
Try this example to check if the input string is an integer
def isInt(s:String) ={
val intRegex = "\\d*".r
s match {
case intRegex() => println(s"${s} is an integer")
case _ => println(s"${s} is not an integer")
}
}
$ isInt("1")
1 is an integer
$ isInt("hello")
hello is not an integer
Try this example to extract the name for string Name=bob
def getName(s:String) = {
val nameRegex = "Name=(.*)".r
s match {
case nameRegex(n) => println(s"The name is ${n}")
case _ => println("No idea")
}
}
$ getName("Name=bob")
The name is bob
$ getName("Wow!")
No idea
The regex in pattern matching will always try to extract something, so even you don't have a group in your regex expression you also need a ()
following the regex.
The dark magic of unapply
The source of truth for pattern matching is unapply
, the pattern matching is just a sugar syntax of unapply
.
For this simple code
v match{
case MyClass(a) => ???
}
We can rewrite it using unapply
val aOption:Option[A]=MyClass.unapply(v)
if(aOption.nonEmpty){
val a = aOption.get
???
}
The declaration of unapply
extract single value
object MyClass{
def unapply(v:MyClass):Option[A]
}A
can be any typeextract multiple values
object MyClass{
def unapply(v:MyClass):Option[(A1,A2,A3,A4)]
}extract unfixed values(the number of values is not fixed)
object MyClass{
def unapplySeq(v:MyClass):Option[Seq[A]]
}
Let's see some declaration of unapply
we used in this chapter, they have been implemented by Scala.
case class
case class MyClass(id:Int)
object MyClass{
def unapply(v:MyClass):Option[Int] = Some(v.id)
}List
object :: {
def unapply[A](v:List[A]):Option[(A,List[A])] = {
if(v.isEmpty) None
else Some(v.head,v.tail)
}
}object List{
def unapplySeq[A](v:List[A]):Option[Seq[A]] = Some(v)
}
We can define a custom extractor by unapply
, it will give us more power.
Try this example, extract the even numbers from List
object Even{
def unapply(v:List[Int]):Option[List[Int]] = Some(v.filter(_%2==0))
}
def getEven(v:List[Int]):List[Int] = {
v match {
case Even(l) => l
case _ => Nil
}
}
$ getEven(List(1,2,3,4,5))
res44: List[Int] = List(2, 4)
$ getEven(List())
res45: List[Int] = List()




