Understanding Scala Extractors: An Easy-to-Follow Example

Search for a command to run...

No comments yet. Be the first to comment.
Go's race detector samples a schedule and hopes. GALA makes a value that isn't safe to share across goroutines a build failure.
June's 0.56 -> 0.62 releases: bind/also monadic do-notation, applicative validation with Validated, and concurrent binds over Future.
Importing a third-party Go module through Bazel and rules_gala - with concrete types across the seam, verified hands-on.
From rules welded inside the language repo to a standalone, registry-published rules_gala with a real toolchain and Gazelle BUILD generation.
Eleven weeks, ~55 releases, and a new tagline: GALA hits 0.50.0 with cross-package generics, an LSP, and four new stdlib packages.
Let's implement a simple case class that represents a Time object that contains hours and minutes. For simplicity, we will use 24-hour clock.
case class Time(hour: Hour, minutes: Minute)
case class Hour(hour: Int)
case class Minute(minute: Int)
Now let's try to implement a code that parses a string into Time using custom extractor
object Time {
def unapply(stringTime: String): Option[Time] = {
stringTime.split(":", 2) match {
case Array(h, m) =>
Some(Time(h, m))
case _ => None
}
}
}
The problem with this code is that hours and minutes are not validated, therefore strings like "30:-1" will still be considered as valid time.
To solve this problem we can embed conditions into time extractor directly or better define separate Hour and Minute extractors with their own validation rules.
object Hour {
def unapply(stringHour: String): Option[Hour] = {
stringHour.toIntOption match {
case Some(h) if h >= 0 && h <= 23 => Some(Hour(h))
case _ => None
}
}
}
object Minute {
def unapply(stringMinute: String): Option[Minute] = {
stringMinute.toIntOption match {
case Some(m) if m >= 0 && m <= 59 => Some(Minute(m))
case _ => None
}
}
}
These extractors allow to extract minutes and hours from string only if they are valid. Now adjust the Time extractor to use extractors for hours and minutes
object Hour {
def unapply(stringHour: String): Option[Hour] = {
stringHour.toIntOption match {
case Some(h) if h >= 0 && h <= 23 => Some(Hour(h))
case _ => None
}
}
}
object Minute {
def unapply(stringMinute: String): Option[Minute] = {
stringMinute.toIntOption match {
case Some(m) if m >= 0 && m <= 59 => Some(Minute(m))
case _ => None
}
}
}
object Time {
def unapply(stringTime: String): Option[Time] = {
stringTime.split(":",2) match {
case Array(Hour(h), Minute(m)) =>
Some(Time(h, m))
case _ => None
}
}
}
Let's test the code on a few unit tests
object TimeExtractors extends App {
List("12:30", "-1:23", "a:b", "4:59", "1:2:3", "12:77").foreach {
case strTime @ Time(t) => println(s"${strTime} converted to ${t}")
case strTime => println(s"unknown time ${strTime}")
}
}
Output from the code above
12:30 converted to Time(Hour(12),Minute(30))
unknown time -1:23
unknown time a:b
4:59 converted to Time(Hour(4),Minute(59))
unknown time 1:2:3
unknown time 12:77
This article demonstrates how to implement a simple Time case class with validation for hours and minutes using extractors in Scala and how to combine multiple extractors to increase readability