简洁的collectFirst超过scala中的嵌套列表

Concise collectFirst over nested lists in scala

我正在用 Scala 编写一个简单的解释器。

这个解释器本质上存储了一个 List[List[(Symbol, Value)]] 环境:

case class Env(frames: List[Frame]) {
   def lookup(s: LSymbol): Option[Value] = ??? // help
}
case class Frame(associations: List[(LSymbol, Value)]){
 def find(s: LSymbol): Option[Value] = {
   this.associations.collectFirst {case (s1, v) if s1 == s => v}
 }
}

我基本上想依次在每一帧中搜索匹配的符号。虽然为此编写一个小的尾递归搜索函数很简单,但感觉可以用 collectFirst 单行代码更有效、更通用地完成一些事情,如下所示:

def lookup(s: LSymbol): Option[Value] = 
 this.frames.collectFirst{
  case frame if frame.find(s).nonEmpty => frame.find(s).get
 }

然而,这在第二个 frame.find(s) 中是多余的。有没有办法以一种不浪费的方式以某种方式简洁地执行此查找?

使用 view 应该使评估变得懒惰,然后你可以像这样使用 mapfilter

def lookup(s: LSymbol): Option[Value] =
  this.frames.view.map(_.find(s)).filter(_.nonEmpty).head

这是单行的,但可能没有您喜欢的那么简洁...

您可以定义一个特殊用途的提取器来提取 frame.find(s) 值。这样你也将摆脱 nonEmpty / get 电话,因为模式匹配已经为你做到了:

def lookup(s: LSymbol): Option[Value] = {
  object FindS {
    def unapply(frame: Frame): Option[Value] = frame.find(s)
  }
  this.frames.collectFirst {
    case FindS(value) => value
  }
}

如果您经常这样做,您甚至可能想要定义一个助手 class 来简化提取器的构建:

class Extractor[T, X](f: T => Option[X]) {
  def unapply(arg: T): Option[X] = f(arg)
}

def lookup(s: LSymbol): Option[Value] = {
  object FindS extends Extractor((frame: Frame) => frame.find(s))
  this.frames.collectFirst {
    case FindS(value) => value
  }
}