【问题标题】:Scala optional pattern matchingScala 可选模式匹配
【发布时间】:2016-12-31 00:25:46
【问题描述】:

我发现自己经常使用返回 Option 的模式匹配,而不匹配的情况返回 None,例如

x match {
    case A(a) => Some(a)
    case B(b) => Some(b)
    case _ => None
}

我可以想象用这个来简化它

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)
    }
}

允许

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

我想知道这种方法是否隐藏了任何陷阱和/或在 Scala 2.11+ 中是否有更简单/更好/更惯用的机制来执行此操作。

更新:我们的目标是在匹配的 rhs 上处理任意计算,这使得基于异常的解决方案不受欢迎。

【问题讨论】:

    标签: scala pattern-matching


    【解决方案1】:

    惯用语:

    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)
    

    或者:

    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)
    

    【讨论】:

      【解决方案2】:

      从选项中收集

      使用get 方法(参见下面给出的实现)将给定值包装在选项周围,然后收集所需的值。

      使用选项包装值,然后收集您想要收集的任何内容。

      Option(x: Any).collect { case 1 => 1 }
      

      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)
      

      使用隐式类的更好的 API

      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
      

      现在您只需 invoke 获取方法并传递偏函数。现在你可能会得到一个包裹在 Some 中的值,或者会得到 None。

      注意上面的API(get方法)不是类型安全的,你可以这样做

       2.get { case str: String => str }
      

      返回无。

      现在,如果您想要类型安全,请进行以下更改

      类型安全

       implicit class InnerValue[A](value: A) {
        def get[B](pf: PartialFunction[A, B]): Option[B] = Option(value) collect pf
       }
      

      注意偏函数的输入参数类型是A,而不是Any。

      现在,当你这样做时

      2.get { case str: String => str }
      

      你会得到编译错误。

      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 }
      

      绕过编译错误

      您可以通过以下操作绕过编译错误

      scala> (2: Any) get { case str: String => str}
      res16: Option[String] = None
      

      【讨论】:

      • 这是我第一次看到(2: Any) 语法。您能否提供有关此行为的一些详细信息,或者可能是 scala-lang 链接?还有,忽略x get {...}x maybeMatch {...}的语法差异,前者有什么优势?
      • @Sim 通过执行2: Any 请求编译器将 2 视为 Any 而不是 Int。使用标准的 scala lib 你可以继续做Option(x: Any).collect { case 1 =&gt; 1 }。 get 使用选项并收集。 getmaybeMatch 做同样的事情。但实现方式不同
      • 如果我们对PartialFunction.condOpt("foo" : Any) { case 5 =&gt; 'good }没有问题,为什么我们不能在隐式类的实现中隐藏(... : Any)以获得更清晰的调用语法?
      • 顺便说一句,将值包装在 Option() 中存在与 null 值的微妙问题,例如,Option(null) collect { case null =&gt; 'good } 将不匹配。这就是为什么直接使用偏函数而不是转换要匹配的值似乎更好的原因。
      【解决方案3】:

      我会这样做:

      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)
      

      在 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
      

      【讨论】:

      • 这是一个糟糕的解决方案,原因有两个。首先,您不能确定 MatchError 被抛出在部分函数的顶层,而不是在匹配案例的其中一个 rhs 的调用堆栈深处。其次,异常很慢。
      • 我假设您的匹配将是简单的情况,例如 A(a) =&gt; a,无需计算。当然解决方案将取决于实际的f
      • 我正在寻找任意 rhs 计算的解决方案,就像 match 一样。
      猜你喜欢
      • 1970-01-01
      • 2016-05-17
      • 2021-12-17
      • 1970-01-01
      • 1970-01-01
      • 2014-09-30
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多