Extractors
File : ExtractorsDemo
When a Java developer sees Pattern Matching for the first time it look like all rules from OOP are broken - all class internals are shown to outside world.
First thing - case classes exists in a very different paradigm than classical java objects
Second thing - topic of todays workshops - you can actually use pattern matching and still have encapsulated private state - how? - extractors
Unapply
Let's have a standard class with private mutable state
class ClassWithPrivateState(initial: String = "") {
private var mutableState: String = initial
def add(chars: String): Unit = mutableState = mutableState + chars
def drop(howMany: Int) = mutableState = mutableState.drop(howMany)
}
It is not a case class so no companion object will be generated. But still we can create our own. We already saw method apply so now time for it brother/sister unapply.
object ClassWithPrivateState {
def apply(initial: String = ""): ClassWithPrivateState = new ClassWithPrivateState(initial)
def unapply(arg: ClassWithPrivateState): Option[String] = Some(arg.mutableState)
}
How is this usable? _unapply _returns Option[A] where A is of extracted field type so in the next example :
instance.add("_added")
instance.drop(3)
instance match {
case ClassWithPrivateState(extracted) => println(s"extracted state $extracted")
}
will display : extracted state tial_added
We did not returned direct reference to private state but we extracted it through unapply. _To see that getter of mutable state and extractor are two seprate things l_et's see another example where we will add pattern matching to a primitive
object SimpleEvenExtractor {
def unapply(i: Int) = if (i % 2 == 0) Some(i) else None
}
and now we can use this extractor with primitive in pattern matching
val i = 2
i match {
case SimpleEvenExtractor(i) => println(s"$i is even")
case other => println(s"$other is not even")
}
This was about filtering and now let's change information format structure
object NameExtractor {
def unapply(fullName: String): Option[(String, String)] = {
val parts: Array[String] = fullName.split(" ")
if (parts.length == 2) Some(parts(0), parts(1)) else None //tuple synthatic sugar!
}
}
"single" match {
case NameExtractor(name, surname) => println(s"extracted name=$name, surname=$surname")
case other => println(s"not a name $other")
}
Extracting Private State
Till now we have just manipulated public state. In the exercises we will see how to treat extractor as intelligent getter which can execute proper get/extraction according to pattern matching.
Exercises
FILE : PatternMatching2Exercises
Exercise1 - Email Extractor
Write simple email extractor which can split string into two parts : name _and _domain
object EmailExtractor {
def unapply(mail: String): Option[(String, String)] = ???
}
Exercise2 - State History
Hawing class :
class StateHistory {
private var history: Vector[Int] = Vector()
def addEvent(e: Int): Unit = history = history.+:(e)
}
Implement extractor which returns number of events if there are any.
Exercise3 - Conditional Extractor
In this exercise you need to write extractor which will 'get' basket content only if it has some content.
So this time unapply method will return Boolean
def unapply(b:Basket) : Boolean = ???
then you will have to implement _process _method to make test passing
result mustBe "processed 2 products"