簡體   English   中英

為什么每個新的Case類實例在Scala中再次評估惰性值?

[英]Why do each new instance of case classes evaluate lazy vals again in Scala?

據我了解,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類的所有實例,只應執行一次惰性val定義一次。 我需要一些記憶技術還是Scala可以自己處理?

我對Scala和函數式編程非常陌生,所以如果您發現此問題微不足道,請原諒。

假設您沒有重寫equals或進行諸如構造函數args var s之類的不良建議,那么情況是兩個具有相同構造函數參數的case類實例化將相等。 但是,這並不意味着與同一構造函數參數的兩個案例類實例將指向內存中的同一個對象

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的重新計算,則可以緩存該部分:

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

object A {
  private[A] val kCache = collection.mutable.Map[Int, Int]()
}

A(5).k     // prints k
A(5).k     // doesn't print anything

簡單的答案是“這是語言根據規范所做的”。 這是正確的,但不是很令人滿意的答案。 為什么這樣做會更有趣。

可能更清楚,它必須使用另一個示例來執行此操作:

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

當構造兩個A ,您不知道它們是否相等,因為尚未定義它們相等意味着什么(或B相等意味着什么)。 而且您也不能靜態初始化k ,因為它取決於傳入的B

如果必須打印兩次,那么只有在k依賴於b的情況下才是完全直觀的,而如果它不依賴於b話則不是那么直觀。

當你問

我應該如何實現它,以便對於具有相同參數的case類的所有實例,只應執行一次惰性val定義一次

這是一個聽起來棘手的問題。 您使“相同參數”聽起來像在編譯時就可以知道的東西,而無需進一步的信息。 不是,您只能在運行時知道它。

而且,如果您僅在運行時知道這一點,則意味着您必須保留實例A[B]所有過去使用。 這是內置的內存泄漏-難怪Scala沒有內置的方法可以做到這一點。

如果您確實要這樣做-並仔細考慮內存泄漏-構造一個Map[B, A[B]] ,然后嘗試從該映射中獲取一個緩存的實例,如果它不存在,則構造一個並把它放在地圖上

我相信case class僅將其構造函數(而不是任何輔助構造函數)的參數視為其相等性概念的一部分。 想想當你在使用的情況下,類match的語句, unapply只給你訪問(默認情況下)的構造函數的參數。

將案例類主體中的任何內容視為“額外”或“副作用”。 我認為這是一個很好的策略,使案例類盡可能接近空,並將任何自定義邏輯放入伴隨對象中。 例如:

case class Foo(a:Int)

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

除了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

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM