Scala 视图——非严格与惰性

Scala views -- non-strict vs. lazy

我正在尝试创建一个 "lazy" 对象图(实际上,它们是演员,但我用一个更简单的例子来问我的问题)。

从某种意义上说,Scala 视图是惰性。但他们的懒惰实际上只是不严格。也就是说,这些值实际上是按名称调用的,这反过来又表示在需要时通过调用 Function0(无参数函数)来评估这些值.

我感兴趣的是延迟计算但只计算一次的集合。这是我正在寻找的东西:

val x = Map(1->2, 2->2).view
val y = x map {case (k,v) => (k,{println("Hello");v.toString})}
val z1 = y.find{case (k,_) => k==1}
val z2 = y.find{case (k,_) => k==1}

当我将其放入 Scala 工作表中时,我得到的是:

x: scala.collection.IterableView[(Int, Int),scala.collection.immutable.Map[Int,Int]] = IterableView(...)
y: scala.collection.IterableView[(Int, String),Iterable[_]] = IterableViewM(...)
Hello
z1: Option[(Int, String)] = Some((1,1))
Hello
z2: Option[(Int, String)] = Some((1,1))

一切都是应该的。除了我不想看到第二个 "Hello"。换句话说,我只希望在需要时调用映射函数 (toString)。

有没有人对如何实现我的目标有建议?这不是特别重要,但我很好奇它是否可以完成。

我会提出替代解决方案,而不是 laziness。 如果您的 v 将发挥作用但没有价值怎么办? 在这种情况下,您可以在需要时控制执行,而无需依赖集合惰性...

val y = x map {case (k,v) => (k,() => {println("Hello");v.toString})}

你可以几乎使用 Stream:

得到你想要的
scala> val x = TreeMap(1->2, 2->2) // to preserve order
x: scala.collection.immutable.TreeMap[Int,Int] = Map(1 -> 2, 2 -> 2)

scala> val y = x.toStream map {case (k,v) => (k,{println(s"Hello $k");v.toString})}
Hello 1
y: scala.collection.immutable.Stream[(Int, String)] = Stream((1,2), ?)

scala> y.find{case (k,_) => k==1}
res8: Option[(Int, String)] = Some((1,2))

scala> y.find{case (k,_) => k==2}
Hello 2
res9: Option[(Int, String)] = Some((2,2))

如你所见,第一个元素被严格评估,但其他元素按需评估和记忆

如果您将流本身设为 lazy val,您会得到想要的:

scala>   val x = TreeMap(1->2, 2->2) // to preserve order
x: scala.collection.immutable.TreeMap[Int,Int] = Map(1 -> 2, 2 -> 2)

scala>   lazy val y = x.toStream map {case (k,v) => (k,{println(s"Hello $k");v.toString})}
y: scala.collection.immutable.Stream[(Int, String)] = <lazy>

scala>   y.find{case (k,_) => k==1}
Hello 1
res10: Option[(Int, String)] = Some((1,2))

scala>   y.find{case (k,_) => k==1}
res11: Option[(Int, String)] = Some((1,2))

如果你不介意在使用时立即评估整个集合,你只需要一个惰性val并且集合可以保持原样(地图,列表等等)

val x = TreeMap(1->2, 2->2) 
lazy val y = x map {case (k,v) => (k,{println(s"Hello $k");v.toString})}

我不认为你可以有一张(真正的)惰性地图,但如果有人证明我错了,我会很高兴:)


编辑: 您可以通过像这样包装您的值来拥有(某种)惰性地图:

class Lazy[T](x: => T) {
  lazy val value = x
  override def toString = value.toString
}

object Lazy {
  implicit def toStrict[T](l: Lazy[T]): T = l.value
}

val x = TreeMap(1->2, 2->2)
lazy val y = x map {case (k,v) => (k, new Lazy({println(s"Hello $k");v.toString}))}

y.find{case (k,v) => v.indexOf("x");k==1} // let's use v to evaluate it, otherwise nothing gets printed
y.find{case (k,v) => v.indexOf("x");k==1}

隐式转换允许您像使用原始类型一样使用您的值

我不知道有哪个合集 API 能提供那种懒惰。但是,我认为您可以按照 here:

中描述的函数记忆来实现您想要的
case class Memo[I <% K, K, O](f: I => O) extends (I => O) {
  import collection.mutable.{Map => Dict}
  val cache = Dict.empty[K, O]
  override def apply(x: I) = cache getOrElseUpdate (x, f(x))
}

val x = Map(1->2, 2->2).view
val memo = Memo { v: Int =>
  println("Hello")
  v.toString
}
val y = x.map { case (k, v) =>
  (k, memo(v))
}
val z1 = y.find{case (k,_) => k==1}
val z2 = y.find{case (k,_) => k==1}

输出:

Hello
z1: Option[(Int, String)] = Some((1,2))
z2: Option[(Int, String)] = Some((1,2))