Scala 中的 lazy Val

Suraj P 2023年1月30日
  1. 在 Scala 中使用 lazy val
  2. lazy val 的無限資料結構
  3. 在 Scala 中使用 lazy val 的優點
  4. 在 Scala 中使用 lazy val 的缺點
Scala 中的 lazy Val

本文將討論 lazy val 如何在 Scala 中工作。我們還將討論它的一些缺點。

在 Scala 中使用 lazy val

lazy val 是一種延遲變數初始化的功能,這是 Java 程式中使用的典型模式。它也被稱為按需呼叫評估策略,其中語句直到第一次使用才被評估,這意味著將評估推遲或推遲到需要時。

我們只需在 val 之前新增關鍵字 lazy 即可使用它。讓我們用一個例子來看看它是如何工作的。

以下程式碼語句在 scala REPL 中執行。

沒有 lazy

scala> val marks = List(20,30,40,50)
val marks: List[Int] = List(20, 30, 40, 50)

scala> val doubleMarks = marks.map(temp=>temp*2)    //when we hit enter after statement , we get below result: the doubleMarks list is calculated
val doubleMarks: List[Int] = List(40, 60, 80, 100)


scala> println(doubleMarks)
List(40, 60, 80, 100)

使用 lazy

scala> val marks = List(50,60,70,25)
val marks: List[Int] = List(50, 60, 70, 25)

scala> lazy val doubleMarks = marks.map(temp=>temp*2)
lazy val doubleMarks: List[Int] // unevaluated

scala> println(doubleMarks)
List(100, 120, 140, 50)

Scala 編譯器不會立即計算 lazy val 表示式。僅在首次訪問時對其進行評估。

當初始訪問完成時,編譯器評估表示式,然後儲存(快取)lazy val 結果。稍後我們訪問它時不會執行任何操作,並且編譯器會返回儲存的結果。

讓我們看一個關於惰性驗證結果的快取/儲存的示例:

scala> lazy val statement={ println("first time access and storing it"); 1000 }
lazy val statement: Int // unevaluated

scala> println(statement)
first time access and storing it
1000

scala> println(statement)
1000

當我們第一次列印/訪問該語句時,我們會得到 1000 和一個列印語句,但是當第二次訪問它時,我們只會得到 1000,因為它是在變數語句中分配和儲存/快取的值。

lazy val 的無限資料結構

lazy val 可以訪問無限資料結構。

在 Scala 中,我們知道列表序列。這些列表也是嚴格序列意味著列表元素是預先構建的,但我們也可以建立非嚴格序列,其中元素是根據要求製作的。

示例程式碼:

object workspace {
    def main(args: Array[String]): Unit = {
        var temp = 5

        def myfun() = {
            temp += 1;
            temp
        }

        lazy val geek = Stream.continually( myfun() )

        (geek take 5) foreach {
            x => println(2*x)
        }
    }
}

輸出:

12
14
16
18
20

myfun() 方法被轉換為一個函式。Stream.continually() 方法建立一個無限流。

它接受該函式,並且每當訪問每個元素時,僅在那時呼叫 myfun() 函式來執行計算。

這種按需執行的函式稱為 Thunk,一旦計算完成,就會儲存在快取中以供進一步使用,這也稱為記憶化。

在 Scala 中使用 lazy val 的優點

lazy val 優化了計算過程。在示例中,我們可以觀察到我們浪費了對映操作(CPU 計算),這在處理更廣泛和更復雜的程式碼時非常昂貴。

所以在這裡,惰性求值通過僅在需要時評估表示式來幫助我們優化過程,避免不必要的計算。

大資料框架 Apace Spark 在處理大量資料時大量利用了此功能。lazy val 還可以訪問無限資料結構。

在 Scala 中使用 lazy val 的缺點

儘管它有優點,但它也有一些缺點,因此必須謹慎使用。

lazy val 在處理多個執行緒時可能會導致死鎖。

示例程式碼:

import scala.concurrent.ExecutionContext.Implicits.global
import scala.concurrent._
import scala.concurrent.duration.DurationInt

object myFirstObj {
    lazy val initialState = 50
    lazy val start = mySecondObj.initialState
}

object mySecondObj {
    lazy val initialState = myFirstObj.initialState
}

object workspace extends App {
    def run = {
        val result = Future.sequence(Seq(Future {
        myFirstObj.start
        },
        Future {
            mySecondObj.initialState
        }
        ))
        Await.result(result, 20.second)
    }
    run
}

輸出:

Exception in thread "main" java.util.concurrent.TimeoutException: Future timed out after [20 seconds]
    at scala.concurrent.impl.Promise$DefaultPromise.tryAwait0(Promise.scala:248)
    at scala.concurrent.impl.Promise$DefaultPromise.result(Promise.scala:261)
    at scala.concurrent.Await$.$anonfun$result$1(package.scala:201)
    at scala.concurrent.BlockContext$DefaultBlockContext$.blockOn(BlockContext.scala:62)
    at scala.concurrent.Await$.result(package.scala:124)
    at workspace$.run(workspace.scala:23)
    at workspace$.delayedEndpoint$workspace$1(workspace.scala:25)
    at workspace$delayedInit$body.apply(workspace.scala:14)
    at scala.Function0.apply$mcV$sp(Function0.scala:39)
    at scala.Function0.apply$mcV$sp$(Function0.scala:39)
    at scala.runtime.AbstractFunction0.apply$mcV$sp(AbstractFunction0.scala:17)
    at scala.App.$anonfun$main$1(App.scala:76)
    at scala.App.$anonfun$main$1$adapted(App.scala:76)
    at scala.collection.IterableOnceOps.foreach(IterableOnce.scala:563)
    at scala.collection.IterableOnceOps.foreach$(IterableOnce.scala:561)
    at scala.collection.AbstractIterable.foreach(Iterable.scala:926)
    at scala.App.main(App.scala:76)
    at scala.App.main$(App.scala:74)
    at workspace$.main(workspace.scala:14)
    at workspace.main(workspace.scala)

我們可以看到我們在這裡得到了一個超時異常。Future 初始化 myFirstObjFirstObj 的例項在內部嘗試初始化 SecondObj

此外,下一步的 Future 嘗試初始化 mySecondObj。這會導致潛在的死鎖情況。

此外,程式的空間複雜度可能會增加,因為我們可能必須儲存/快取大量表示式。由於程式設計師無法控制程式的執行,因此除錯在這裡可能會變得很棘手。

作者: Suraj P
Suraj P avatar Suraj P avatar

A technophile and a Big Data developer by passion. Loves developing advance C++ and Java applications in free time works as SME at Chegg where I help students with there doubts and assignments in the field of Computer Science.

LinkedIn GitHub