स्काला लक्षण में वैल या डिफ का उपयोग कब करें?


90

मैं के माध्यम से जा रहा था प्रभावी स्केला स्लाइड और यह स्लाइड 10 पर उल्लेख है का उपयोग कभी नहीं करने के लिए valएक में traitसार के सदस्यों और उपयोग के लिए defबजाय। स्लाइड में विस्तार से उल्लेख नहीं किया गया है कि valए में सार का उपयोग traitकरना एक विरोधी पैटर्न क्यों है। मैं इसकी सराहना करूंगा अगर कोई अमूर्त तरीकों के लिए किसी विशेषता में वैल बनाम डिफ का उपयोग करते हुए सर्वश्रेष्ठ अभ्यास के बारे में बता सके

जवाबों:


130

defको ए def, ए val, lazy valया ए में से किसी के द्वारा लागू किया जा सकता है object। तो यह एक सदस्य को परिभाषित करने का सबसे सार रूप है। चूंकि लक्षण आमतौर पर अमूर्त इंटरफेस होते हैं, इसलिए आप चाहते हैं कि एक valकह रहा है कि कार्यान्वयन कैसे करना चाहिए। यदि आप एक के लिए पूछते हैं val, तो एक कार्यान्वयन वर्ग ए का उपयोग नहीं कर सकता है def

valकी आवश्यकता केवल तभी होती है जब आपको एक स्थिर पहचानकर्ता की आवश्यकता होती है, उदाहरण के लिए पथ-निर्भर प्रकार। यह ऐसी चीज है जिसकी आपको आमतौर पर जरूरत नहीं होती है।


की तुलना करें:

trait Foo { def bar: Int }

object F1 extends Foo { def bar = util.Random.nextInt(33) } // ok

class F2(val bar: Int) extends Foo // ok

object F3 extends Foo {
  lazy val bar = { // ok
    Thread.sleep(5000)  // really heavy number crunching
    42
  }
}

अगर आप के पास था

trait Foo { val bar: Int }

आप परिभाषित F1या नहीं कर पाएंगे F3


ठीक है, और आपको भ्रमित करने के लिए और उत्तर देने में @ om-nom-nom- अमूर्त vals का उपयोग करने से प्रारंभिक समस्याएं हो सकती हैं:

trait Foo { 
  val bar: Int 
  val schoko = bar + bar
}

object Fail extends Foo {
  val bar = 33
}

Fail.schoko  // zero!!

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

संपादित करें (जनवरी 2016): आपको valएक lazy valकार्यान्वयन के साथ एक सार घोषणा को ओवरराइड करने की अनुमति है , ताकि प्रारंभिक विफलता को भी रोका जा सके।


8
मुश्किल आरंभीकरण आदेश और आश्चर्यजनक नल के बारे में शब्द?
om-nom-nom

हाँ ... मैं वहाँ भी नहीं जाऊँगा। सच है कि ये भी घाटी के खिलाफ तर्क हैं, लेकिन मुझे लगता है कि मूल प्रेरणा सिर्फ कार्यान्वयन को छिपाने के लिए होनी चाहिए।
0__

2
यह हाल के स्काला संस्करण (इस टिप्पणी के रूप में 2.11.4) में बदल गया है, लेकिन आप एक के valसाथ ओवरराइड कर सकते हैं lazy val। अपने दावे आप बनाने में सक्षम नहीं होगा कि F3अगर barएक valसही नहीं है। कहा गया है, लक्षण में सार सदस्यों को हमेशा def
बजे

Foo / Fail उदाहरण अपेक्षा के अनुरूप काम करता है यदि आप के val schoko = bar + barसाथ प्रतिस्थापित करते हैं lazy val schoko = bar + bar। आरंभीकरण क्रम पर कुछ नियंत्रण रखने का यह एक तरीका है। इसके अलावा, व्युत्पन्न वर्ग के lazy valबजाय का उपयोग करने defसे पुनर्मिलन से बचा जाता है।
एड्रियन

2
यदि आप अभी भी शून्य में बदल val bar: Intजाते हैं def bar: Int Fail.schoko
जैस्पर-एम

8

मैं valलक्षणों का उपयोग नहीं करना पसंद करता हूं क्योंकि वैल डिक्लेरेशन में अस्पष्ट और गैर-सहज ज्ञान युक्त क्रम है। आप पहले से काम कर रहे पदानुक्रम में एक विशेषता जोड़ सकते हैं और यह उन सभी चीजों को तोड़ देगा जो पहले काम करती थीं, मेरा विषय देखें: गैर-अंतिम कक्षाओं में सादे घाटी का उपयोग क्यों करें

आपको इस वैल डिक्लेरेशन का उपयोग करने के बारे में सभी बातों को ध्यान में रखना चाहिए जो अंततः आपको एक त्रुटि के लिए सड़क करती है।


अधिक जटिल उदाहरण के साथ अद्यतन करें

लेकिन ऐसे समय होते हैं जब आप उपयोग करने से बच नहीं सकते थे val। जैसा कि @ 0__ ने उल्लेख किया है कि कभी-कभी आपको एक स्थिर पहचानकर्ता की आवश्यकता होती है और defएक नहीं है।

मैं यह दिखाने के लिए एक उदाहरण प्रदान करूंगा कि वह किस बारे में बात कर रहा था:

trait Holder {
  type Inner
  val init : Inner
}
class Access(val holder : Holder) {
  val access : holder.Inner =
    holder.init
}
trait Access2 {
  def holder : Holder
  def access : holder.Inner =
    holder.init
}

यह कोड त्रुटि उत्पन्न करता है:

 StableIdentifier.scala:14: error: stable identifier required, but Access2.this.holder found.
    def access : holder.Inner =

यदि आपको लगता है कि आपको यह समझने में एक मिनट लगेगा कि संकलक के पास शिकायत करने का कारण है। में Access2.accessमामला यह किसी भी तरह से प्राप्त वापसी प्रकार नहीं कर सके। def holderइसका मतलब है कि इसे व्यापक तरीके से लागू किया जा सकता है। यह प्रत्येक कॉल के लिए अलग-अलग धारकों को लौटा सकता है और यह धारक विभिन्न Innerप्रकारों को शामिल करेंगे । लेकिन जावा वर्चुअल मशीन को उसी प्रकार की वापसी की उम्मीद है।


3
आरंभीकरण के आदेश से कोई फर्क नहीं पड़ता, लेकिन इसके बजाय हमें NPE के रनटाइम के दौरान, एंटी-विज़-इन-एंटी-पैटर्न मिलता है।
जोनाथन न्युफेल्ड

scala में घोषणात्मक वाक्यविन्यास होता है जो अनिवार्य प्रकृति को पीछे छिपा देता है। कभी-कभी यह
अपरिपक्वता

-4

हमेशा डिफ का उपयोग करना थोड़ा अजीब लगता है क्योंकि कुछ इस तरह से काम नहीं करेगा:

trait Entity { def id:Int}

object Table { 
  def create(e:Entity) = {e.id = 1 }  
}

आपको निम्न त्रुटि मिलेगी:

error: value id_= is not a member of Entity

2
कोई प्रासंगिक नहीं। यदि आपके पास def (त्रुटि: reassignment to val) के बजाय वैल का उपयोग होता है, तो भी आपके पास एक त्रुटि है, और यह पूरी तरह से तार्किक है।
वोलिया १17

यदि आप उपयोग नहीं करते हैं var। मुद्दा यह है कि, यदि वे क्षेत्र हैं तो उन्हें इस तरह नामित किया जाना चाहिए। मुझे लगता है कि सब कुछ defकम से कम देखा जा रहा है।
दिमित्री

@Dimitry, निश्चित रूप से, varचलो का उपयोग करके आप एनकैप्सुलेशन को तोड़ते हैं। लेकिन ग्लोबल वेरिएबल पर def(या ए val) का उपयोग करना पसंद किया जाता है। मुझे लगता है कि आप जिस चीज की तलाश कर रहे हैं वह कुछ case class ConcreteEntity(override val id: Int) extends Entityऐसा है जिससे आप इसे बना सकते हैं यह def create(e: Entity) = ConcreteEntity(1)एनकैप्सुलेशन को तोड़ने से अधिक सुरक्षित है और किसी भी वर्ग को एंटिटी को बदलने की अनुमति देता है।
जोनो
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.