Question: Scala optional pattern matching

Question

Scala optional pattern matching

Answers 3
Added at 2016-12-29 05:12
Tags
Question

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.

Answers to

Scala optional pattern matching

nr: #1 dodano: 2016-12-29 06:12

Collect from Option

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)

Nicer API using Implicit Class

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

Type safety

 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 }

Get around the compilation error

You can get around the compilation error by doing following

scala> (2: Any) get { case str: String => str}
res16: Option[String] = None
nr: #2 dodano: 2016-12-29 06:12

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)
nr: #3 dodano: 2016-12-30 09:12

I would do it like this:

def trial(a:Any, f:Any => Any) = try Some(f(a)) catch {case _:MatchError => None}

implicit class BetterAny(a:Any) {
   def betterMatch(f:Any => Any) = trial(a, f)
}

// some example classes
case class A(i:Int)
case class B(s:String)

On the REPL:

scala> A(1) betterMatch {case A(a) => a; case B(b) => b}
res11: Option[Any] = Some(1)

scala> 2 betterMatch {case A(a) => a; case B(b) => b}
res12: Option[Any] = None
Source Show
◀ Wstecz