Scala 多层次模式匹配

3

我被多层模式匹配难住了,在下面的代码中,我想匹配一个特定的情况,在几个级别上进行检查 "cfe是赋值语句,assignmentCfe.getRight是二元表达式,等等",解决方案看起来很丑陋,我希望Scala有更好的解决方法。 :)

  def findAll(cfg: Cfg, consumer: Consumer[Violation]): Unit = {
    val fa = new FlowAnalyzer
    val states = fa.analyze(cfg)

    states.foreach { case (cfe, state) => cfe match {
      case assign: Assignment => assign.getRight match {
        case expression: BinaryExpression => expression.getOperator match {
          case Operator.Div | Operator.Rem => processDivisions()
          case _ =>
        }
        case _ =>
      }
      case _ =>
    }
    case _ =>
    }
  }

如何去除结尾处的空默认情况?

另一种方法是使用嵌套条件,但IntelliJ IDEA建议我将这些条件替换为模式匹配

states.foreach { case (cfe, state) => if (cfe.isInstanceOf[Assignment]) {
      val assignment = cfe.asInstanceOf[Assignment]
      if (assignment.getRight.isInstanceOf[BinaryExpression]) {
        val expression = assignment.getRight.asInstanceOf[BinaryExpression]
        if (expression.getOperator == Operator.Div || expression.getOperator == Operator.Rem) processDivisions()
      }
    }}

states的类型是什么?在很多地方似乎可以使用.filter - Tyler
@Tyler 表示 Map[Cfe, State] 中的 Cfe 可能是许多不同的东西。在过滤后,我是否需要以某种方式“转换”我的条目? - okutane
如果没有这些数据结构的确切定义,我们无法真正提供帮助。从写法来看,似乎你只是使用了匹配(matches)而不是isInstanceOfs(特别是Div | Rem部分)。这让我想问,模式匹配(pattern match)是否真的是你想要的工具?如果这些是case类,为什么不使用嵌套值匹配所有这些内容呢? - pedrofurla
@pedrofurla Div | Rem 是最内部的条件,我已更新了我的问题,以便您可以了解我正在尝试匹配什么。 - okutane
我非常喜欢你的第二个解决方案。别误会,我仍然认为它很丑陋,但至少它易于阅读和快速理解。你在处理类型Unit的情况下,泄漏了isInstanceOfs,不要试图假装它是函数式代码,这是match/cases的目的。 - pedrofurla
3个回答

3
< p> 赋值表达式(Assignment)二进制表达式(BinaryExpression)本身是样例类吗?还是它们有相应的unapply方法?如果是这样,那么您可以嵌套模式匹配并忽略您不关心的字段。例如,像这样:

def findAll(cfg: Cfg, consumer: Consumer[Violation]): Unit = {
  val fa = new FlowAnalyzer
  val states = fa.analyze(cfg)

  states.foreach {
    case (Assignment(_, BinaryExpression(_, _, Operator.Div | Operator.Rem)), _) => processDivisions()
    case _ =>
  }
}

这样至少可以将默认匹配项的数量减少到1个。

如果这些不是case类或没有提取器,那么您可以考虑在代码中编写自己的提取器对象,如果这是一个常见的(反)模式:http://docs.scala-lang.org/tutorials/tour/extractor-objects.html


实际上它们并不是来自本模块的,而是来自另一个用Java编写的模块。但你的示例正是我所需要的,因此我会考虑编写提取器以更好地与那段Java代码集成。 - okutane

0
另一个想法是,您可以使用“pimp my library”模式,将任何对象转换为一个类,该类可以进行部分匹配。
class PartialMatcher[A](a: A) {
  def partialMatch(f: PartialFunction[A, Unit]): Unit = if (f.isDefinedAt(a)) f(a)
}
implicit def makePartialMatcher[A](a: A) = new PartialMatcher(a)

然后只需用partialMatch替换所有这些匹配项:

  def findAll(cfg: Cfg, consumer: Consumer[Violation]): Unit = {
    val fa = new FlowAnalyzer
    val states = fa.analyze(cfg)

    states.foreach { case (cfe, state) => cfe partialMatch {
      case assign: Assignment => assign.getRight partialMatch {
        case expression: BinaryExpression => expression.getOperator partialMatch {
          case Operator.Div | Operator.Rem => processDivisions()
        }
      }
    }}
  }

请注意,还有其他原因可能会让您避免这种情况...过度使用隐式转换可能会使代码的理解变得更加困难。这是一种风格上的选择。

这里有什么缺失吗?我已经添加了PartialMatcher并切换到你的代码,但是有关于partialMatch的报告。 - okutane

0

使用 .collect

def findAll(cfg: Cfg, consumer: Consumer[Violation]): Unit = {
  val fa = new FlowAnalyzer
  val states = fa.analyze(cfg)
  states.collect { case (assign: Assignment, _) => 
    assign.getRight
  }.collect { case expression: BinaryExpression => 
    expression.getOperator
  }.collect { case Operator.Div | Operator.Rem => 
    processDivisions
  }

我无法通过这种方式访问processDivisions中的“assign”。 - okutane
当然不行。它是在外部定义的,你没有传递它进去。 - Dima

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接