为什么每个新的case类实例都会在Scala中再次评估延迟值?

时间:2016-07-24 18:50:25

标签: scala functional-programming

根据我的理解,scala将val定义视为值。 因此,具有相同参数的案例类的任何实例都应该相等。 但是,

case class A(a: Int) {
   lazy val k = {
       println("k")
       1
   }

 val a1 = A(5)
 println(a1.k)
 Output:
 k
 res1: Int = 1

 println(a1.k)
 Output:
 res2: Int = 1

 val a2 = A(5)
 println(a1.k)
 Output:
 k
 res3: Int = 1

我期待println(a2.k),它不应该打印k。 由于这不是必需的行为,我应该如何实现这个以便对于具有相同参数的case类的所有实例,它应该只执行一次lazy val定义。我是否需要一些memoization技术或者Scala可以单独处理这个问题吗?

我是Scala和函数式编程的新手,所以如果你发现这个问题很简单,请原谅。

4 个答案:

答案 0 :(得分:2)

假设你没有覆盖npm install ionic-gulp-browserify-typescript@1.1.0 --save-dev 或做一些不明智的事情,比如使构造函数args equals s,那么两个具有相同构造函数参数的case case实例将是相同的。但是,意味着具有相同构造函数参数的两个案例类实例将指向内存中的相同对象

var

如果您希望构造函数始终在内存中返回相同的对象,那么您需要自己处理。也许使用某种工厂:

case class A(a: Int)
A(5) == A(5)  // true, same as `A(5).equals(A(5))`
A(5) eq A(5)  // false

相反,如果您不关心返回相同对象的构造函数,但您只关心case class A private (a: Int) { lazy val k = { println("k") 1 } } object A { private[this] val cache = collection.mutable.Map[Int, A]() def build(a: Int) = { cache.getOrElseUpdate(a, A(a)) } } val x = A.build(5) x.k // prints k val y = A.build(5) y.k // doesn't print anything x == y // true x eq y // true 的重新评估,则可以缓存该部分:

k

答案 1 :(得分:1)

琐碎的答案是"这就是语言根据规范所做的事情。这是正确的,但不是很令人满意的答案。它为什么会这样做更有趣。

可能更清楚的是 用另一个例子来做这件事:

case class A[B](b: B) {
   lazy val k = {
       println(b)
       1
   }
}

当你构建两个A时,你无法知道它们是否相等,因为你还没有定义它们相等的含义(或者说B等于是什么意思)。并且您也无法对k进行静态初始化,因为它取决于传入的B

如果必须打印两次,那么只有k取决于b的情况才是完全直观的,但如果它不依赖b则不会}}

当你问

  

我应该如何实现这一点,以便对于具有相同参数的case类的所有实例,它只应该只执行一次lazy val定义

这是一个比听起来更棘手的问题。你做了相同的参数"听起来像编译时可以知道的东西没有进一步的信息。不是,你只能在运行时知道它。

如果您只是在运行时知道,那意味着您必须保持实例A[B]的所有过去使用活动。这是一个内置的内存泄漏 - 难怪Scala没有内置的方法来做到这一点。

如果你真的想要这个 - 并且长时间地考虑内存泄漏 - 构建一个Map[B, A[B]],并尝试从该地图获取一个缓存的实例,如果它不存在,构建一个并把它放在地图上。

答案 2 :(得分:0)

我相信case class只考虑其构造函数(不是任何辅助构造函数)的参数,使其成为 equality 概念的一部分。考虑在match语句中使用案例类时,unapply仅允许您访问(默认情况下)构造函数参数。

在案例类的主体中考虑任何事情" extra"或"副作用"东西。我认为将case类尽可能接近空并将任何自定义逻辑放在一个伴随对象中是一个很好的策略。例如:

case class Foo(a:Int)

object Foo {
    def apply(s: String) = Foo(s.toInt)
}

答案 3 :(得分:0)

除了dhg回答之外,我应该说,我不知道默认情况下完全构造函数记忆的函数式语言。你应该明白,这样的记忆意味着所有构造的实例都应该留在记忆中,这并不总是令人满意的。

手动缓存并不难,请考虑这个简单的代码

import scala.collection.mutable

class Doubler private(a: Int) {
  lazy val double = {
    println("calculated")
    a * 2
  }
}

object Doubler{
  val cache = mutable.WeakHashMap.empty[Int, Doubler]
  def apply(a: Int): Doubler = cache.getOrElseUpdate(a, new Doubler(a))
}

Doubler(1).double   //calculated

Doubler(5).double   //calculated

Doubler(1).double   //most probably not calculated