Extractor behavior can be used to derive arbitrary values from their input. This can be useful in scenarios where you want to be able to act on the results of a transformation in the event that the transformation is successful.
Consider as an example the various user name formats usable in a Windows environment:
object UserPrincipalName {
def unapply(str: String): Option[(String, String)] = str.split('@') match {
case Array(u, d) if u.length > 0 && d.length > 0 => Some((u, d))
case _ => None
}
}
object DownLevelLogonName {
def unapply(str: String): Option[(String, String)] = str.split('\\') match {
case Array(d, u) if u.length > 0 && d.length > 0 => Some((d, u))
case _ => None
}
}
def getDomain(str: String): Option[String] = str match {
case UserPrincipalName(_, domain) => Some(domain)
case DownLevelLogonName(domain, _) => Some(domain)
case _ => None
}
In fact it is possible to create an extractor exhibiting both behaviors by broadening the types it can match:
object UserPrincipalName {
def unapply(obj: Any): Option[(String, String)] = obj match {
case upn: UserPrincipalName => Some((upn.username, upn.domain))
case str: String => str.split('@') match {
case Array(u, d) if u.length > 0 && d.length > 0 => Some((u, d))
case _ => None
}
case _ => None
}
}
In general, extractors are simply a convenient reformulation of the Option
pattern, as applied to methods with names like tryParse
:
UserPrincipalName.unapply("user@domain") match {
case Some((u, d)) => ???
case None => ???
}