scala 在正文中使用模式匹配守卫 (if) 的计算

scala using calculations from pattern matching's guard (if) in body

我经常在 Scala 中使用模式匹配。很多时候我需要在守卫部分做一些计算,有时它们非常昂贵。有没有办法将计算值绑定到单独的值?

//i wan't to use result of prettyExpensiveFunc in body safely
people.collect {
  case ...
  case Some(Right((x, y))) if prettyExpensiveFunc(x, y) > 0 => prettyExpensiveFunc(x)
}

//ideally something like that could be helpful, but it doesn't compile:
people.collect {
  case ...
  case Some(Right((x, y))) if {val z = prettyExpensiveFunc(x, y); y > 0} => z
}

//this sollution works but it isn't safe for some `Seq` types and is risky when more cases are used.
var cache:Int = 0
people.collect {
  case ...
  case Some(Right((x, y))) if {cache = prettyExpensiveFunc(x, y); cache > 0} => cache
}

有没有更好的解决办法? ps:示例已简化,我不希望显示我在这里不需要模式匹配的答案。

为什么不先 运行 每个元素的函数,然后再处理元组?

Seq(1,2,3,4,5).map(e => (e, prettyExpensiveFunc(e))).collect {
  case ...
  case (x, y) if y => y
}

您可以使用 cats.Eval 使昂贵的计算变得惰性和可记忆,使用 .map 创建 Eval 并提取 .value (最多计算一次 - 如果需要)在 .collect

values.map { value =>
  val expensiveCheck1 = Eval.later { prettyExpensiveFunc(value) }
  val expensiveCheck2 = Eval.later { anotherExpensiveFunc(value) }
  (value, expensiveCheck1, expensiveCheck2)
}.collect {
  case (value, lazyResult1, _) if lazyResult1.value > 0 => ...
  case (value, _, lazyResult2) if lazyResult2.value > 0 => ...
  case (value, lazyResult1, lazyResult2) if lazyResult1.value > lazyResult2.value => ...
  ...
}

我看不出有什么方法可以在不创建惰性求值实现的情况下做你想做的事情,如果你必须使用一个,你最好使用现有的而不是自己滚动一个。

编辑。以防万一你没有注意到 - 你不会失去通过在此处使用元组进行模式匹配的能力:

values.map {
  // originial value -> lazily evaluated memoized expensive calculation
  case a @ Some(Right((x, y)) => a -> Some(Eval.later(prettyExpensiveFunc(x, y)))
  case a                      => a -> None
}.collect {
  // match type and calculation
  ...
  case (Some(Right((x, y))), Some(lazyResult)) if lazyResult.value > 0 => ...
  ...
}

我尝试了自己的匹配器,效果还可以,但并不完美。我的匹配器是无类型的,让它完全类型化有点难看。

class Matcher[T,E](f:PartialFunction[T, E]) {
  def unapply(z: T): Option[E] = if (f.isDefinedAt(z)) Some(f(z)) else None
}

def newMatcherAny[E](f:PartialFunction[Any, E]) = new Matcher(f)
def newMatcher[T,E](f:PartialFunction[T, E]) = new Matcher(f)

def prettyExpensiveFunc(x:Int) = {println(s"-- prettyExpensiveFunc($x)"); x%2+x*x}

val x = Seq(
  Some(Right(22)),
  Some(Right(10)), 
  Some(Left("Oh now")), 
  None
)
val PersonAgeRank = newMatcherAny { case Some(Right(x:Int)) => (x, prettyExpensiveFunc(x)) }

x.collect { 
  case PersonAgeRank(age, rank) if rank > 100 => println("age:"+age + " rank:" + rank) 
}

https://scalafiddle.io/sf/hFbcAqH/3