2011-09-20 15 views
193

मैंने देखा कि स्कैला 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 

REPL पता चलता है कि y एक lazy val है, लेकिन यह कैसे एक सामान्य val से अलग है?

उत्तर

267

उनके बीच का अंतर यह है कि 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 = [email protected] // we have to wait two seconds to the result 

scala> new Y 
res6: Y = [email protected] // this appears immediately 

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

यदि आप जानना चाहते हैं कि lazy vals लागू किए गए हैं, तो यह question देखें।

+55

एक पूरक के रूप में: @ViktorKlang ट्विटर पर पोस्ट किया गया: ["छोटे ज्ञात स्कैला तथ्य: यदि आलसी वैल की शुरुआत एक exce फेंकता है पशन, यह अगले पहुंच पर वैल को फिर से शुरू करने का प्रयास करेगा। "] (https://twitter.com/#!/viktorklang/status/104483846002704384) –

51

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

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 

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

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() 
+0

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

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 

यह एक "जल्दी प्रारंभकर्ता" कहा जाता है। अधिक जानकारी के लिए this SO question देखें।

+11

क्या आप स्पष्टीकरण दे सकते हैं कि वाई की घोषणा क्यों माता-पिता कन्स्ट्रक्टर को कॉल करने से पहले पहले उदाहरण में चर "x" को प्रारंभ नहीं करती है? – Ashoat

+2

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

+0

@Ashoat कृपया यह लिंक देखें (https://github.com/scala/scala.github.com/blob/master/tutorials/FAQ/initialization-order.md) इसकी व्याख्या के लिए क्यों इसे प्रारंभ नहीं किया गया है। – Jus12

21

एक आलसी वैल को "memoized def" के रूप में आसानी से समझा जाता है।

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

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

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

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

23

मैं समझता हूँ कि इस सवाल का जवाब दिया जाता है लेकिन मैं इसे आसान मेरे जैसे शुरुआती के लिए समझने के लिए बनाने के लिए एक सरल उदाहरण लिखा है:

var x = { println("x"); 15 } 
lazy val y = { println("y"); x+1 } 
println("-----") 
x = 17 
println("y is: " + y) 
ऊपर दिए गए कोड की

आउटपुट है:

x 
----- 
y 
y is: 18 

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

उम्मीद है कि इससे मदद मिलती है।

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

scala> lazyEight 
I am lazy ! 
res1: Int = 8 
  • सभी Vals वस्तु निर्माण के दौरान प्रारंभ कर रहे हैं
  • उपयोग आलसी कीवर्ड सर्वप्रथम प्रयोग जब तक प्रारंभ स्थगित करने
  • ध्यान: Lazy Vals अंतिम और इसलिए प्रदर्शन दिखा सकता है नहीं कर रहे हैं कमियां
संबंधित मुद्दे