【问题标题】:Scala views -- non-strict vs. lazyScala 视图——非严格与懒惰
【发布时间】:2016-07-24 07:32:45
【问题描述】:

我正在尝试创建对象的“惰性”映射(实际上,它们是演员,但我用一个更琐碎的例子来问我的问题)。

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

我感兴趣的是一个惰性求值但只求值一次的集合。这是我正在寻找的东西:

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

一切都应该如此。除了我不想看到第二个“你好”。换句话说,我只希望映射函数 (toString) 在需要时被调用一次。

有人对如何实现我的目标提出建议吗?这不是超级重要,但我很好奇它是否可以做到。

【问题讨论】:

  • z1z2有什么区别?
  • @rumoku 没有区别,他只是想记住 y 以便每个元素不会调用两次 map 函数

标签: scala lazy-evaluation


【解决方案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))

【讨论】:

  • 是的,谢谢,我正在考虑这些方面的事情。正如你所指出的,它并没有做我真正想要的,但它会做的工作,如果有点不雅:)
【解决方案2】:

你可以几乎使用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}

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

【讨论】:

  • 感谢您的(更新)回答。您为此付出了很多努力,我真的很感激。它似乎确实实现了我正在寻找的东西(如此赞成)。我不太清楚为什么您将 v.indexOf("x") 插入谓词中,但我删除了它并且它按应有的方式工作。然而,底线是它几乎不简单(或优雅)并且在记忆模式上并没有真正改进。谢谢:)
  • 我以indexOf("x")为例,评论里写着:let's use v to evaluate it, otherwise nothing gets printed。如果您从不以某种方式使用该值,您将看不到任何打印:)
【解决方案3】:

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

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

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2023-03-18
    • 2012-11-04
    • 1970-01-01
    • 2017-01-21
    • 1970-01-01
    • 1970-01-01
    • 2017-04-30
    • 2019-10-12
    相关资源
    最近更新 更多