Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Scala optional pattern matching

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.

like image 946
Sim Avatar asked Feb 05 '23 10:02

Sim


2 Answers

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)
like image 137
som-snytt Avatar answered Feb 21 '23 05:02

som-snytt


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
like image 38
pamu Avatar answered Feb 21 '23 05:02

pamu