I find myself frequently using pattern matching that returns an Option
with the no match case returning None
, e.g.
x match {
case A(a) => Some(a)
case B(b) => Some(b)
case _ => None
}
I can imagine simplifying this using
object MaybeMatchImplicits {
implicit class MaybeMatcher[A](val underlying: A) extends AnyVal {
@inline
def maybeMatch[B](f: PartialFunction[A, B]): Option[B] = f.lift.apply(underlying)
}
}
which allows
scala> import MaybeMatchImplicits._
import MaybeMatchImplicits._
scala> 5 maybeMatch { case 5 => 'good }
res0: Option[Symbol] = Some('good)
scala> 6 maybeMatch { case 5 => 'good }
res1: Option[Symbol] = None
I am wondering if this approach hides any gotchas and/or if there is a simpler/better/more idiomatic mechanism for doing this in Scala 2.11+.
Update: The goal is to handle arbitrary computation on the rhs of matches, which makes exception-based solutions undesirable.
Idiomatic:
scala> case class A(a: Int) ; case class B(b: String)
defined class A
defined class B
scala> def f(x: Any) = Option(x) collect { case A(a) => a ; case B(b) => b }
f: (x: Any)Option[Any]
scala> f(42)
res0: Option[Any] = None
scala> f(A(42))
res1: Option[Any] = Some(42)
scala> f(B("ok"))
res2: Option[Any] = Some(ok)
Alternatively:
scala> import PartialFunction.{cond => when, condOpt => whenever}
import PartialFunction.{cond=>when, condOpt=>whenever}
scala> def f(x: Any) = whenever(x) { case A(a) => a ; case B(b) => b }
f: (x: Any)Option[Any]
scala> f(42)
res3: Option[Any] = None
scala> f(A(42))
res4: Option[Any] = Some(42)
scala> f(B("ok"))
res5: Option[Any] = Some(ok)
Use get
method (see the implementation given below) which wraps the given value around option and then collects required value.
Wrap the value using option and then collect what ever you want to collect.
Option(x: Any).collect { case 1 => 1 }
or
x get { case 2 => 2 } // get implementation is given below
Scala REPL
scala> Option(1).collect { case 1 => 1 }
res0: Option[Int] = Some(1)
scala> Option(2).collect { case str: String => "bad" }
<console>:12: error: scrutinee is incompatible with pattern type;
found : String
required: Int
Option(2).collect { case str: String => "bad" }
^
scala> Option(2: Any).collect { case str: String => "bad" }
res2: Option[String] = None
scala> Option(2: Any).collect { case 2 => "bad" }
res3: Option[String] = Some(bad)
implicit class InnerValue[A](value: A) {
def get[B](pf: PartialFunction[Any, B]): Option[B] = Option(value) collect pf
}
Scala REPL
scala> implicit class InnerValue[A](value: A) {
| def get[B](pf: PartialFunction[Any, B]): Option[B] = Option(value) collect pf
| }
defined class InnerValue
scala> 2.get { case 2 => 2}
res5: Option[Int] = Some(2)
scala> 2.get { case 3 => 2}
res6: Option[Int] = None
Now you can just invoke
get method and pass the partial function. Now you may get a value wrapped in Some or will get None.
Notice that the above API (get method) is not type safe, you can do
2.get { case str: String => str }
Which returns None.
Now if you want typesafe make the following change
implicit class InnerValue[A](value: A) {
def get[B](pf: PartialFunction[A, B]): Option[B] = Option(value) collect pf
}
Notice in the partial function the input parameter type is A
instead of Any.
Now, when you do
2.get { case str: String => str }
You will get compilation error.
scala> 2.get { case str: String => str }
<console>:15: error: scrutinee is incompatible with pattern type;
found : String
required: Int
2.get { case str: String => str }
You can get around the compilation error by doing following
scala> (2: Any) get { case str: String => str}
res16: Option[String] = None
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