आलसी वैल क्या करता है?


248

मैंने देखा कि स्काला प्रदान करता है lazy vals। लेकिन मुझे नहीं मिलता कि वे क्या करते हैं।

scala> val x = 15
x: Int = 15

scala> lazy val y = 13
y: Int = <lazy>

scala> x
res0: Int = 15

scala> y
res1: Int = 13

आरईपीएल शो है कि yएक है lazy val, लेकिन यह कैसे एक सामान्य से अलग है val?

जवाबों:


335

उनके बीच का अंतर यह है, कि ए valको तब परिभाषित किया जाता है जब इसे परिभाषित किया जाता है जबकि ए lazy valको तब निष्पादित किया जाता है जब इसे पहली बार एक्सेस किया जाता है।

scala> val x = { println("x"); 15 }
x
x: Int = 15

scala> lazy val y = { println("y"); 13 }
y: Int = <lazy>

scala> x
res2: Int = 15

scala> y
y
res3: Int = 13

scala> y
res4: Int = 13

एक विधि के विपरीत (परिभाषित def) एक lazy valबार निष्पादित होता है और फिर कभी नहीं। यह तब उपयोगी हो सकता है जब किसी ऑपरेशन को पूरा होने में लंबा समय लगता है और जब बाद में उपयोग किया जाता है तो यह सुनिश्चित नहीं होता है।

scala> class X { val x = { Thread.sleep(2000); 15 } }
defined class X

scala> class Y { lazy val y = { Thread.sleep(2000); 13 } }
defined class Y

scala> new X
res5: X = X@262505b7 // we have to wait two seconds to the result

scala> new Y
res6: Y = Y@1555bd22 // this appears immediately

यहां, जब मूल्यों xऔर yकभी उपयोग नहीं किया जाता है, तो केवल xअनावश्यक रूप से संसाधनों को बर्बाद करना। अगर हमें लगता है कि yइसका कोई साइड इफेक्ट नहीं है और हमें नहीं पता कि यह कितनी बार एक्सेस किया जाता है (कभी नहीं, एक बार, हजारों बार) तो इसे घोषित करना बेकार है defक्योंकि हम इसे कई बार निष्पादित नहीं करना चाहते हैं।

यदि आप जानना चाहते हैं कि कैसे lazy valsलागू किया जाता है, तो इस प्रश्न को देखें ।


65
एक पूरक के रूप में: @ViktorKlang ने ट्विटर पर पोस्ट किया: "अल्पज्ञात स्काला तथ्य: यदि एक आलसी
पीटर शमित्ज़

@PeterSchmitz और मुझे यह भयानक लगता है। Lazy<T>.NET
Pavel Voronin

61

यह सुविधा न केवल महंगी गणना में देरी करने में मदद करती है, बल्कि पारस्परिक निर्भर या चक्रीय संरचनाओं के निर्माण के लिए भी उपयोगी है। उदाहरण के लिए यह एक ढेर अतिप्रवाह की ओर जाता है:

trait Foo { val foo: Foo }
case class Fee extends Foo { val foo = Faa() }
case class Faa extends Foo { val foo = Fee() }

println(Fee().foo)
//StackOverflowException

लेकिन आलसी वैल के साथ यह ठीक काम करता है

trait Foo { val foo: Foo }
case class Fee extends Foo { lazy val foo = Faa() }
case class Faa extends Foo { lazy val foo = Fee() }

println(Fee().foo)
//Faa()

लेकिन यह एक ही StackOverflowException के लिए नेतृत्व करेंगे अगर आपके toString विधि "फू" विशेषता आउटपुट। वैसे भी "आलसी" का अच्छा उदाहरण !!!
फुआद एफेंदी

39

मैं समझता हूं कि उत्तर दिया गया है, लेकिन मैंने अपने जैसे शुरुआती लोगों के लिए इसे समझना आसान बनाने के लिए एक सरल उदाहरण लिखा है:

var x = { println("x"); 15 }
lazy val y = { println("y"); x+1 }
println("-----")
x = 17
println("y is: " + y)

उपरोक्त कोड का आउटपुट है:

x
-----
y
y is: 18

जैसा कि देखा जा सकता है, जब इसे इनिशियलाइज़ किया जाता है तो x प्रिंट किया जाता है, लेकिन y तब प्रिंट नहीं किया जाता है जब इसे उसी तरीके से इनिशियलाइज़ किया जाता है (मैंने एक्स को जानबूझकर यहाँ लिया है - यह समझाने के लिए कि y इनिशियलाइज़ हो जाता है)। इसके बाद जब y कहा जाता है, तो इसे प्रारंभिक रूप में और साथ ही अंतिम 'x' के मूल्य को ध्यान में रखा जाता है, लेकिन पुराने को नहीं।

उम्मीद है की यह मदद करेगा।


35

एक आलसी घाटी को सबसे आसानी से " ज्ञापन ( अर्ग ) नहीं " के रूप में समझा जाता है ।

एक बचाव की तरह, एक आलसी घाटी का मूल्यांकन तब तक नहीं किया जाता है जब तक कि इसे लागू नहीं किया जाता है। लेकिन परिणाम सहेजा जाता है ताकि बाद में किए गए इनवॉइस सहेजे गए मान को वापस करें। ज्ञापन परिणाम आपके डेटा संरचना में एक घाटी की तरह जगह लेता है।

जैसा कि दूसरों ने उल्लेख किया है, एक आलसी घाटी के लिए उपयोग के मामले महंगी संगणनाओं को टालने के लिए होते हैं, जब तक उन्हें ज़रूरत होती है और अपने परिणामों को संग्रहीत करते हैं, और मूल्यों के बीच कुछ परिपत्र निर्भरता को हल करने के लिए।

आलसी वैल्स वास्तव में याद दोष के रूप में कम या ज्यादा कार्यान्वित होते हैं। आप उनके कार्यान्वयन के विवरण के बारे में यहां पढ़ सकते हैं:

http://docs.scala-lang.org/sips/pending/improved-lazy-val-initialization.html


1
हो सकता है कि इसके बजाय "मेमोराइज्ड डीफ जो कि 0 तर्क लेता है"।
एंड्रे टायुकिन

19

lazyनिम्न कोड के अनुसार चक्रीय निर्भरता के बिना भी उपयोगी है:

abstract class X {
  val x: String
  println ("x is "+x.length)
}

object Y extends X { val x = "Hello" }
Y

एक्सेस Yकरना अब शून्य पॉइंटर अपवाद को फेंक देगा, क्योंकि xअभी तक इनिशियलाइज़ नहीं किया गया है। हालांकि, निम्नलिखित ठीक काम करता है:

abstract class X {
  val x: String
  println ("x is "+x.length)
}

object Y extends X { lazy val x = "Hello" }
Y

संपादित करें: निम्नलिखित भी काम करेगा:

object Y extends { val x = "Hello" } with X 

इसे "प्रारंभिक आरंभीकरण" कहा जाता है। देखें इस तो सवाल यह अधिक जानकारी के लिए।


11
क्या आप स्पष्ट कर सकते हैं कि पैरेंट कंस्ट्रक्टर को कॉल करने से पहले वाई की घोषणा पहले उदाहरण में चर "x" को तुरंत क्यों नहीं शुरू करती है?
अशोअत

2
क्योंकि सुपरक्लास कंस्ट्रक्टर पहले एक है जिसे निहित रूप से कहा जाता है।
स्टेवो स्लाविक

@Ashoat कृपया इस लिंक को स्पष्टीकरण के लिए देखें कि यह क्यों आरंभिक नहीं है।
जुस 12

4

का एक प्रदर्शन lazy- जैसा कि ऊपर बताया गया है - निष्पादन जब परिभाषित बनाम निष्पादन जब एक्सेस किया जाता है: (2.12.7 स्केला शेल का उपयोग करके)

// compiler says this is ok when it is lazy
scala> lazy val t: Int = t 
t: Int = <lazy>
//however when executed, t recursively calls itself, and causes a StackOverflowError
scala> t             
java.lang.StackOverflowError
...

// when the t is initialized to itself un-lazily, the compiler warns you of the recursive call
scala> val t: Int = t
<console>:12: warning: value t does nothing other than call itself recursively
   val t: Int = t

1
scala> lazy val lazyEight = {
     |   println("I am lazy !")
     |   8
     | }
lazyEight: Int = <lazy>

scala> lazyEight
I am lazy !
res1: Int = 8
  • ऑब्जेक्ट वैल्यू निर्माण के दौरान सभी वैल्यू को इनिशियलाइज़ किया जाता है
  • पहले उपयोग तक आरंभीकरण को स्थगित करने के लिए आलसी कीवर्ड का उपयोग करें
  • ध्यान दें : आलसी वैल अंतिम नहीं हैं और इसलिए प्रदर्शन कमियां दिखा सकते हैं
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.