आश्रित विधि प्रकारों के लिए कुछ सम्मोहक उपयोग के मामले क्या हैं?


127

आश्रित विधि प्रकार, जो पहले एक प्रायोगिक विशेषता हुआ करती थी, अब ट्रंक में डिफ़ॉल्ट रूप से सक्षम हो गई है , और जाहिर तौर पर ऐसा लगता है कि स्काला समुदाय में कुछ उत्तेजना पैदा हुई है।

पहली नज़र में, यह तुरंत स्पष्ट नहीं है कि यह किस लिए उपयोगी हो सकता है। हेइको सीबर्गर ने यहां आश्रित विधि प्रकारों का एक सरल उदाहरण पोस्ट किया है , जैसा कि टिप्पणी में देखा जा सकता है कि विधियों पर प्रकार के मापदंडों के साथ आसानी से पुन: पेश किया जा सकता है। तो यह एक बहुत ही आकर्षक उदाहरण नहीं था। (मुझे कुछ स्पष्ट याद आ रहा है। कृपया मुझे ठीक करें अगर ऐसा है।)

आश्रित विधि प्रकारों के लिए उपयोग के मामलों के कुछ व्यावहारिक और उपयोगी उदाहरण क्या हैं जहां वे विकल्पों पर स्पष्ट रूप से लाभप्रद हैं?

हम उनके साथ क्या दिलचस्प चीजें कर सकते हैं जो पहले संभव / आसान नहीं थे?

वे मौजूदा प्रकार के सिस्टम फीचर्स से हमें क्या खरीदते हैं?

इसके अलावा, अन्य उन्नत टाइप की गई भाषाओं जैसे Haskell, OCaml के सिस्टम में पाए जाने वाले किसी भी प्रकार की प्रेरणा से निर्भर या टाइप करने वाली प्रेरणा के प्रकार हैं?


आपकी रुचि perk पर नहीं
Dan Burton

लिंक के लिए धन्यवाद, Dan! मैं सामान्य रूप से आश्रित प्रकारों से अवगत हूं, लेकिन आश्रित विधि प्रकारों की अवधारणा मेरे लिए अपेक्षाकृत नई है।
लापता

यह मुझे लगता है कि "आश्रित विधि प्रकार" बस प्रकार होते हैं जो एक या अधिक विधि के इनपुट प्रकारों पर निर्भर होते हैं (उस वस्तु के प्रकार सहित जिस पर विधि लागू होती है); कुछ भी नहीं पागल वहाँ निर्भर प्रकार के सामान्य विचार से परे। शायद मुझे कुछ याद आ रहा है?
डैन बर्टन

नहीं, आपने नहीं किया, लेकिन जाहिर है मैंने किया। :-) मैंने पहले दोनों के बीच लिंक नहीं देखा था। यह हालांकि अब स्पष्ट है।
जुलाई

जवाबों:


112

कम या ज्यादा सदस्य (अर्थात नेस्टेड) ​​प्रकारों का उपयोग निर्भर विधि के प्रकार की आवश्यकता को जन्म दे सकता है। विशेष रूप से, मैं यह सुनिश्चित करता हूं कि निर्भर विधि के बिना क्लासिक केक पैटर्न एक विरोधी पैटर्न होने के करीब है।

तो समस्या क्या है? स्काला में नेस्टेड प्रकार उनके संलग्न उदाहरण पर निर्भर हैं। नतीजतन, आश्रित विधि प्रकारों की अनुपस्थिति में, उस उदाहरण के बाहर उनका उपयोग करने का प्रयास निराशाजनक रूप से कठिन हो सकता है। यह डिजाइनों को चालू कर सकता है जो शुरू में सुरुचिपूर्ण लगते हैं और मठों में आकर्षक लगते हैं जो बुरे तरीके से कठोर होते हैं और रिफ्लेक्टर के लिए मुश्किल होते हैं।

मैं बताता हूं कि मैं अपने एडवांस स्काला प्रशिक्षण पाठ्यक्रम के दौरान एक अभ्यास के साथ देता हूं ,

trait ResourceManager {
  type Resource <: BasicResource
  trait BasicResource {
    def hash : String
    def duplicates(r : Resource) : Boolean
  }
  def create : Resource

  // Test methods: exercise is to move them outside ResourceManager
  def testHash(r : Resource) = assert(r.hash == "9e47088d")  
  def testDuplicates(r : Resource) = assert(r.duplicates(r))
}

trait FileManager extends ResourceManager {
  type Resource <: File
  trait File extends BasicResource {
    def local : Boolean
  }
  override def create : Resource
}

class NetworkFileManager extends FileManager {
  type Resource = RemoteFile
  class RemoteFile extends File {
    def local = false
    def hash = "9e47088d"
    def duplicates(r : Resource) = (local == r.local) && (hash == r.hash)
  }
  override def create : Resource = new RemoteFile
}

यह क्लासिक केक पैटर्न का एक उदाहरण है: हमारे पास अमूर्तता का एक परिवार है जिसे धीरे-धीरे एक उत्तराधिकार के माध्यम से परिष्कृत किया जाता है ( ResourceManager/ Resourceद्वारा परिष्कृत किया जाता है FileManager/ Fileजो बदले में NetworkFileManager/ द्वारा परिष्कृत होते हैं RemoteFile)। यह एक खिलौना उदाहरण है, लेकिन पैटर्न वास्तविक है: इसका उपयोग स्काला कंपाइलर में किया जाता है और इसका उपयोग स्केल एक्लिप्स प्लगइन में बड़े पैमाने पर किया जाता है।

यहाँ उपयोग में अमूर्तता का एक उदाहरण है,

val nfm = new NetworkFileManager
val rf : nfm.Resource = nfm.create
nfm.testHash(rf)
nfm.testDuplicates(rf)

ध्यान दें कि पथ निर्भरता का मतलब है कि संकलक गारंटी देगा कि testHashऔर testDuplicatesविधियों को NetworkFileManagerकेवल उन तर्कों के साथ बुलाया जा सकता है जो इसके अनुरूप हैं, अर्थात। यह अपना है RemoteFiles, और कुछ नहीं।

यह निर्विवाद रूप से एक वांछनीय संपत्ति है, लेकिन मान लीजिए कि हम इस परीक्षण कोड को एक अलग स्रोत फ़ाइल में स्थानांतरित करना चाहते थे? आश्रित विधि प्रकारों के साथ यह ResourceManagerपदानुक्रम के बाहर उन विधियों को फिर से परिभाषित करने के लिए बहुत आसान है ,

def testHash4(rm : ResourceManager)(r : rm.Resource) = 
  assert(r.hash == "9e47088d")

def testDuplicates4(rm : ResourceManager)(r : rm.Resource) = 
  assert(r.duplicates(r))

यहां निर्भर विधि प्रकारों के उपयोग पर ध्यान दें: दूसरे तर्क का प्रकार ( rm.Resource) पहले तर्क के मूल्य पर निर्भर करता है ( rm)।

आश्रित विधि प्रकारों के बिना ऐसा करना संभव है, लेकिन यह बहुत ही अजीब है और तंत्र काफी अचूक है: मैं इस पाठ्यक्रम को लगभग दो वर्षों से सिखा रहा हूं, और उस समय में, कोई भी काम नहीं कर रहा है, जो बिना किसी समाधान के लागू हुआ है।

इसे अपने लिए आजमाएं ...

// Reimplement the testHash and testDuplicates methods outside
// the ResourceManager hierarchy without using dependent method types
def testHash        // TODO ... 
def testDuplicates  // TODO ...

testHash(rf)
testDuplicates(rf)

इसके साथ संघर्ष करते हुए थोड़े समय के बाद आप शायद यह जान पाएंगे कि मैं (या शायद यह डेविड मैकाइवर क्यों था, हम याद नहीं कर सकते कि हममें से किसने यह शब्द गढ़ा था) इसे बेकरी ऑफ डूम कहते हैं।

संपादित करें: सर्वसम्मति है कि बेकरी ऑफ डूम डेविड मैकाइवर का सिक्का था ...

बोनस के लिए: स्कैला का सामान्य प्रकार में आश्रित प्रकार (और इसके एक भाग के रूप में आश्रित विधि प्रकार) प्रोग्रामिंग भाषा बीटा से प्रेरित था ... वे स्वाभाविक रूप से बीटा के सुसंगत घोंसले के शिकार से उत्पन्न होते हैं। मैं किसी अन्य भी बेहोश मुख्यधारा प्रोग्रामिंग भाषा के बारे में नहीं जानता, जिसके इस रूप में निर्भर प्रकार हैं। Coq, Cayenne, Epigram और Agda जैसी भाषाओं में आश्रित टाइपिंग का एक अलग रूप है, जो कुछ मायनों में अधिक सामान्य है, लेकिन जो कि स्केला के विपरीत, टाइप सिस्टम का हिस्सा होने के कारण काफी अलग है, इसमें सबटाइपिंग नहीं है।


2
यह डेविड मैकाइवर था जिसने इस शब्द को गढ़ा, लेकिन किसी भी मामले में, यह काफी वर्णनात्मक है। यह एक शानदार स्पष्टीकरण है कि आश्रित विधि प्रकार इतने रोमांचक क्यों हैं। अच्छा काम!
डैनियल स्पाइवेक

यह कुछ समय पहले #scala पर हम दोनों के बीच बातचीत में शुरू में आया था ... जैसा कि मैंने कहा कि मुझे याद नहीं है कि हम में से कौन यह पहले कहा था।
मील्स सबिन

लगता है कि मेरी स्मृति मुझ पर चालें खेल रही थी ... आम सहमति यह डेविड मैकाइवर का सिक्का था।
माइल्स सबिन

हाँ, मैं उस समय (#scala पर) नहीं था, लेकिन जॉर्ज थे और यहीं से मुझे मेरी जानकारी मिल रही थी।
डैनियल स्पाइवेक

अमूर्त प्रकार के सदस्य शोधन का उपयोग कर मैं काफी दर्द रहित रूप से testHash4 फ़ंक्शन को लागू करने में सक्षम था। def testHash4[R <: ResourceManager#BasicResource](rm: ResourceManager { type Resource = R }, r: R) = assert(r.hash == "9e47088d")मुझे लगता है कि यह निर्भर प्रकारों का एक और रूप माना जा सकता है, हालांकि।
मार्को वैन हिलस्ट

53
trait Graph {
  type Node
  type Edge
  def end1(e: Edge): Node
  def end2(e: Edge): Node
  def nodes: Set[Node]
  def edges: Set[Edge]
}

कहीं और हम सांख्यिकीय रूप से गारंटी दे सकते हैं कि हम दो अलग-अलग ग्राफ़ से नोड्स नहीं मिला रहे हैं, जैसे:

def shortestPath(g: Graph)(n1: g.Node, n2: g.Node) = ... 

बेशक, यह पहले से ही काम कर रहा है अगर अंदर परिभाषित किया गया है Graph, लेकिन कहते हैं कि हम संशोधित नहीं कर सकते हैं Graphऔर इसके लिए "मेरे पुस्तकालय" एक्सटेंशन को लिख रहे हैं।

दूसरे प्रश्न के बारे में: इस सुविधा द्वारा सक्षम प्रकार पूर्ण आश्रित प्रकारों की तुलना में बहुत कमज़ोर हैं ( उस के स्वाद के लिए अगाडा में निर्भर टाइपिंग प्रोग्रामिंग देखें ।) मुझे नहीं लगता कि मैंने पहले एक सादृश्य देखा है।


6

इस नई सुविधा की आवश्यकता तब होती है जब ठोस अमूर्त प्रकार के सदस्यों का उपयोग प्रकार के मापदंडों के बजाय किया जाता है । जब प्रकार के मापदंडों का उपयोग किया जाता है, तो परिवार के बहुरूपता प्रकार की निर्भरता को स्काला के नवीनतम और कुछ पुराने संस्करणों में व्यक्त किया जा सकता है, जैसा कि निम्नलिखित सरलीकृत उदाहरण में है।

trait C[A]
def f[M](a: C[M], b: M) = b
class C1 extends C[Int]
class C2 extends C[String]

f(new C1, 0)
res0: Int = 0
f(new C2, "")
res1: java.lang.String = 
f(new C1, "")
error: type mismatch;
 found   : C1
 required: C[Any]
       f(new C1, "")
         ^

यह असंबंधित है। प्रकार के सदस्यों के साथ आप एक ही परिणाम के लिए शोधन का उपयोग कर सकते हैं: trait C {type A}; def f[M](a: C { type A = M}, b: M) = 0;class CI extends C{type A=Int};class CS extends C{type A=String}आदि
nafg

किसी भी मामले में इसका आश्रित विधि प्रकारों से कोई लेना-देना नहीं है। उदाहरण के लिए एलेक्सी का उदाहरण लें ( stackoverflow.com/a/7860821/333643 )। अपने दृष्टिकोण का उपयोग (शोधन संस्करण सहित मैंने टिप्पणी की) लक्ष्य प्राप्त नहीं करता है। यह सुनिश्चित करेगा कि n1.Node =: = n2.Node, लेकिन यह सुनिश्चित नहीं करेगा कि वे दोनों एक ही ग्राफ़ में हैं। IIUC DMT यह सुनिश्चित करता है।
nafg

@nafg यह इंगित करने के लिए धन्यवाद। मैंने यह स्पष्ट करने के लिए ठोस शब्द जोड़ा है कि मैं टाइप सदस्यों के लिए शोधन मामले का जिक्र नहीं कर रहा था। जहाँ तक मैं देख सकता हूँ, यह अभी भी अपनी बात (जो मुझे पता था) के बावजूद आश्रित विधि प्रकारों के लिए एक वैध उपयोग-मामला है कि वे अन्य उपयोग के मामलों में अधिक शक्ति पा सकते हैं। या मुझे आपकी दूसरी टिप्पणी का मौलिक सार याद आया?
शेल्बी मूर III

3

मैं पर्यावरणीय स्थिति के साथ घोषणात्मक प्रोग्रामिंग के एक रूप को अपनाने के लिए एक मॉडल विकसित कर रहा हूं । विवरण यहाँ प्रासंगिक नहीं हैं (उदाहरण के लिए कॉलबैक और वैचारिक समानता के बारे में विवरण जो एक्टर मॉडल के साथ एक धारावाहिक के साथ संयुक्त है)।

प्रासंगिक मुद्दा यह है कि राज्य मान हैश मानचित्र में संग्रहीत होते हैं और हैश कुंजी मान द्वारा संदर्भित होते हैं। फ़ंक्शंस इनपुट अपरिवर्तनीय तर्क जो पर्यावरण से मान हैं, ऐसे अन्य कार्यों को कॉल कर सकते हैं, और पर्यावरण के लिए स्थिति लिख सकते हैं। लेकिन फ़ंक्शंस को पर्यावरण से मान पढ़ने की अनुमति नहीं है (इसलिए फ़ंक्शन का आंतरिक कोड राज्य परिवर्तनों के आदेश पर निर्भर नहीं है और इस तरह उस अर्थ में घोषणात्मक रहता है)। इसे Scala में कैसे टाइप करें?

पर्यावरण वर्ग में एक अतिभारित विधि होनी चाहिए जो ऐसे फ़ंक्शन को कॉल करने के लिए इनपुट करती है, और फ़ंक्शन के तर्कों की हैश कुंजी इनपुट करती है। इस प्रकार यह विधि फ़ंक्शन को हैश मैप से आवश्यक मानों के साथ कॉल कर सकती है, सार्वजनिक मूल्यों को पढ़ने के लिए उपयोग किए बिना (इस प्रकार आवश्यकतानुसार, पर्यावरण से मूल्यों को पढ़ने की क्षमता से इनकार करते हुए)।

लेकिन अगर इन हैश कुंजी स्ट्रिंग या पूर्णांक हैश मान, हैश मैप तत्व प्रकार के स्थिर टाइपिंग हैं subsumes किसी भी या AnyRef को (हैश नक्शा कोड के नीचे दिखाया गया है), और इस तरह एक रन-टाइम बेमेल हो सकता है, यानी यह संभव होगा किसी दिए गए हैश कुंजी के लिए हैश मैप में किसी भी प्रकार का मान रखने के लिए।

trait Env {
...
  def callit[A](func: Env => Any => A, arg1key: String): A
  def callit[A](func: Env => Any => Any => A, arg1key: String, arg2key: String): A
}

हालाँकि, मैंने निम्नलिखित का परीक्षण नहीं किया है, सिद्धांत रूप में मैं रन टाइम एंप्लॉयमेंट में क्लास के नामों से हैश कीज़ प्राप्त कर सकता हूं classOf, इसलिए एक स्ट्रिंग के बजाय एक हैश कुंजी एक क्लास नाम है (एक क्लास नाम में स्ट्रिंग को एम्बेड करने के लिए स्काला के बैकटिक्स का उपयोग करके)।

trait DependentHashKey {
  type ValueType
}
trait `the hash key string` extends DependentHashKey {
  type ValueType <: SomeType
}

तो स्थिर प्रकार की सुरक्षा प्राप्त की जाती है।

def callit[A](arg1key: DependentHashKey)(func: Env => arg1key.ValueType => A): A

जब हमें तर्क कुंजियों को एक ही मान में पास करने की आवश्यकता होती है, तो मैंने परीक्षण नहीं किया, लेकिन मान लें कि हम 2 तर्क अधिभार के लिए एक टपल का उपयोग कर सकते हैं, जैसे def callit[A](argkeys: Tuple[DependentHashKey,DependentHashKey])(func: Env => argkeys._0.ValueType => argkeys._1.ValueType => A): A। हम तर्क कुंजियों के संग्रह का उपयोग नहीं करेंगे, क्योंकि संग्रह के प्रकार में तत्व प्रकारों को संकलित किया जाएगा (संकलन के समय अज्ञात)।
शेल्बी मूर III

"हैश मैप एलिमेंट की स्टैटिक टाइपिंग किसी भी या किसी भी रीफ्रंट को सब्सक्राइब करती है" - मैं फॉलो नहीं करता। जब आप कहते हैं कि तत्व प्रकार का मतलब है कि आप कुंजी प्रकार या मूल्य प्रकार (यानी पहले या दूसरे प्रकार के तर्क HashMap)? और यह क्यों निर्वाह होगा?
रॉबिन ग्रीन

@RobinGreen हैश तालिका में मानों का प्रकार। अफेयर, निर्वाह क्योंकि आप स्काला में एक संग्रह में एक से अधिक प्रकार नहीं डाल सकते हैं जब तक कि आप उनके सामान्य सुपरस्क्रिप्ट को नहीं मानते हैं, क्योंकि स्काला में एक संघ (अव्यवस्था) प्रकार नहीं है। स्कैला में मेरे क्यूएंडए को देखिए।
शेल्बी मूर तृतीय
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.