59

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

Concretely, कुछ विशेषता T[_] एक typeclass का प्रतिनिधित्व करने दिया, और प्रकार A, B और C, T[A] गुंजाइश, T[B] और T[C] में इसी implicits साथ, हम एक List[T[a] forAll { type a }] की तरह कुछ है, जो में हम A के उदाहरण फेंक कर सकते हैं की घोषणा करना चाहते हैं , B और C अपमान के साथ। यह निश्चित रूप से स्कैला में मौजूद नहीं है; एक question last year अधिक गहराई में इस पर चर्चा करता है।

प्राकृतिक अनुवर्ती प्रश्न यह है कि "हास्केल यह कैसे करता है?" खैर, विशेष रूप से जीएचसी "Boxy Types" पेपर में वर्णित impredicative polymorphism नामक एक प्रकार का सिस्टम एक्सटेंशन है। संक्षेप में, एक टाइपक्लास T दिया गया है, कोई कानूनी रूप से एक सूची [forall a. T a => a] बना सकता है। इस फ़ॉर्म की घोषणा को देखते हुए, कंपाइलर कुछ शब्दकोश-गुजरने वाले जादू करता है जो हमें रनटाइम पर सूची में प्रत्येक मान के प्रकारों के अनुरूप टाइपक्लास उदाहरणों को बनाए रखने देता है।

बात यह है कि, "शब्दकोश-गुजरने वाला जादू" बहुत कुछ "vtables" जैसा लगता है। स्काला जैसी ऑब्जेक्ट-ओरिएंटेड भाषा में, सबटाइपिंग "बॉक्सी प्रकार" दृष्टिकोण की तुलना में एक बहुत ही सरल, प्राकृतिक तंत्र है। यदि हमारे A, B और C सभी विशेषता T का विस्तार करते हैं, तो हम केवल List[T] घोषित कर सकते हैं और खुश रह सकते हैं। इसी प्रकार, जैसा कि माइल्स नीचे एक टिप्पणी में नोट करता है, यदि वे सभी T1, T2 और T3 गुणों का विस्तार करते हैं तो मैं List[T1 with T2 with T3] का उपयोग अपमानजनक हास्केल [forall a. (T1 a, T2 a, T3 a) => a] के बराबर के रूप में कर सकता हूं।

हालांकि, मुख्य, जाने-माने typeclasses की तुलना में subtyping साथ नुकसान तंग युग्मन है: मेरे A, B और C प्रकार उनकी T व्यवहार में पकाया करना होगा मान लेते हैं यह एक बड़ी dealbreaker है, और मैं कर सकते हैं। टी उप प्रकार का उपयोग करें। तो स्काला में बीच मैदान दलाल^एच^एच^एच^एच^Himplicit रूपांतरण है: दिए गए कुछ A => T, B => T और निहित दायरे में C => T, मैं फिर से काफी खुशी से एक List[T] मेरी A, B और C मूल्यों के साथ पॉप्युलेट कर सकते हैं ...

... जब तक हम List[T1 with T2 with T3] नहीं चाहते हैं। उस बिंदु पर, भले ही हमारे पास A => T1, A => T2 और A => T3 अंतर्निहित रूपांतरण हैं, हम सूची में A नहीं डाल सकते हैं। हम अपने अंतर्निहित रूपांतरणों को शाब्दिक रूप से A => T1 with T2 with T3 प्रदान करने के लिए पुन: स्थापित कर सकते हैं, लेकिन मैंने कभी भी ऐसा नहीं देखा है, और ऐसा लगता है कि यह तंग युग्मन का एक और रूप है।

ठीक है, तो मेरे सवाल का अंत है, मुझे लगता है, कुछ सवाल का एक संयोजन है कि पहले यहां पूछा गया: "why avoid subtyping?" और "advantages of subtyping over typeclasses" ... वहाँ कुछ एकीकृत सिद्धांत का कहना है कि impredicative बहुरूपता और उप-प्रकार बहुरूपता एक और एक ही कर रहे हैं ? क्या किसी भी तरह से गुप्त प्यार-दोनों के गुप्त प्यार हैं? और क्या कोई स्केल में कई सीमाओं (ऊपर के अंतिम उदाहरण के रूप में) व्यक्त करने के लिए एक अच्छा, साफ पैटर्न व्यक्त कर सकता है?

+2

यह प्रासंगिक हो सकता है: http://stackoverflow.com/questions/7213676/forall-in-scala – missingfaktor

+3

@ missingfaktor यह निश्चित रूप से है, इसलिए मैंने इसे लिंक किया है! – mergeconflict

+0

आह, क्षमा करें, पहले पढ़ने में याद किया! – missingfaktor

उत्तर

21

आप अस्तित्वहीन प्रकारों के साथ अपमानजनक प्रकारों को भ्रमित कर रहे हैं।आकस्मिक प्रकार आपको डेटा संरचना में पॉलिमॉर्फिक मानों को रखने की अनुमति देता है, मनमाने ढंग से ठोस नहीं। दूसरे शब्दों में [forall a. Num a => a] का अर्थ है कि आपके पास एक सूची है जहां प्रत्येक तत्व किसी भी संख्यात्मक प्रकार के रूप में काम करता है, इसलिए आप उदा। Int और Double टाइप [forall a. Num a => a] की सूची में, लेकिन आप इसमें 0 :: Num a => a जैसे कुछ डाल सकते हैं। अपरिहार्य प्रकार वह नहीं है जो आप यहां चाहते हैं।

आप जो चाहते हैं वह अस्तित्व के प्रकार हैं, यानी [exists a. Num a => a] (वास्तविक हास्केल वाक्यविन्यास नहीं), जो कहता है कि प्रत्येक तत्व कुछ अज्ञात संख्यात्मक प्रकार है।

data SomeNumber = forall a. Num a => SomeNumber a 

नोट exists से forall करने के लिए परिवर्तन: हास्केल में यह लिखने के लिए, तथापि, हम एक आवरण डेटा प्रकार लागू करने के लिए की जरूरत है। ऐसा इसलिए है क्योंकि हम कन्स्ट्रक्टर का वर्णन कर रहे हैं। हम में किसी भी संख्यात्मक प्रकार डाल सकते हैं, लेकिन फिर टाइप सिस्टम "भूल जाता है" यह किस प्रकार था। एक बार जब हम इसे वापस ले लें (पैटर्न मिलान करके), हम सब जानते हैं कि यह कुछ संख्यात्मक प्रकार है। हुड के तहत क्या हो रहा है, यह है कि SomeNumber प्रकार में एक छिपी हुई फ़ील्ड है जो टाइप क्लास डिक्शनरी (उर्फ। Vtable/implicit) स्टोर करती है, यही कारण है कि हमें रैपर प्रकार की आवश्यकता है।

अब हम मनमानी संख्याओं की सूची के लिए [SomeNumber] प्रकार का उपयोग कर सकते हैं, लेकिन हमें प्रत्येक नंबर को रास्ते में लपेटने की आवश्यकता है, उदा। [SomeNumber (3.14 :: Double), SomeNumber (42 :: Int)]। प्रत्येक प्रकार के लिए सही शब्दकोश को उस बिंदु पर स्वचालित रूप से छिपे हुए फ़ील्ड में देखा और संग्रहीत किया जाता है जहां हम प्रत्येक नंबर को लपेटते हैं।

अस्तित्व के प्रकार और प्रकार वर्गों का संयोजन कुछ प्रकारों में उप-प्रकार के समान है, क्योंकि प्रकार वर्गों और इंटरफेस के बीच मुख्य अंतर यह है कि प्रकार वर्गों के साथ vtable वस्तुओं से अलग से यात्रा करता है, और अस्तित्व के प्रकार पैकेज ऑब्जेक्ट्स और vtables वापस फिर एकसाथ।

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

data TwoNumbers = forall a. Num a => TwoNumbers a a 

f :: TwoNumbers -> TwoNumbers 
f (TwoNumbers x y) = TwoNumbers (x+y) (x*y) 

list1 = map f [TwoNumbers (42 :: Int) 7, TwoNumbers (3.14 :: Double) 9] 
-- ==> [TwoNumbers (49 :: Int) 294, TwoNumbers (12.14 :: Double) 28.26] 

या यहां तक ​​कि प्रशंसक चीजें भी। एक बार जब हम रैपर पर पैटर्न मिलान करते हैं, तो हम टाइप क्लास की भूमि में वापस आ जाते हैं। हालांकि हम नहीं जानते कि किस प्रकार x और y हैं, हम जानते हैं कि वे वही हैं, और हमारे पास संख्यात्मक संचालन करने के लिए सही शब्दकोश उपलब्ध है।

उपर्युक्त सबकुछ कई प्रकार के वर्गों के समान काम करता है। कंपाइलर प्रत्येक vtable के लिए रैपर प्रकार में छिपे हुए फ़ील्ड उत्पन्न करेगा और जब हम पैटर्न मिलान करेंगे तो उन्हें सभी को दायरे में लाएंगे।

data SomeBoundedNumber = forall a. (Bounded a, Num a) => SBN a 

g :: SomeBoundedNumber -> SomeBoundedNumber 
g (SBN n) = SBN (maxBound - n) 

list2 = map g [SBN (42 :: Int32), SBN (42 :: Int64)] 
-- ==> [SBN (2147483605 :: Int32), SBN (9223372036854775765 :: Int64)] 

के रूप में मैं बहुत ज्यादा अभी शुरुआत कर रहा हूँ जब यह स्काला की बात आती है, मुझे यकीन है कि मैं अपने प्रश्न के अंतिम भाग के साथ मदद कर सकते हैं नहीं कर रहा हूँ, लेकिन मुझे आशा है कि यह कम से कम भ्रम का कुछ को मंजूरी दी है और आपको आगे बढ़ने के बारे में कुछ विचार दिए।

1

@ हैमर का जवाब बिल्कुल सही है। यहां इसे करने का स्केल तरीका है।उदाहरण के लिए मैं प्रकार वर्ग के रूप में Show ले लेंगे और मूल्यों i और d एक सूची में पैक करने के लिए:

// The type class 
trait Show[A] { 
    def show(a : A) : String 
} 

// Syntactic sugar for Show 
implicit final class ShowOps[A](val self : A)(implicit A : Show[A]) { 
    def show = A.show(self) 
} 

implicit val intShow = new Show[Int] { 
    def show(i : Int) = "Show of int " + i.toString 
} 

implicit val stringShow = new Show[String] { 
    def show(s : String) = "Show of String " + s 
} 


val i : Int = 5 
val s : String = "abc" 

क्या हम चाहते हैं सक्षम रन निम्नलिखित कोड

val list = List(i, s) 
for (e <- list) yield e.show 

बिल्डिंग में आयोजित होना है सूची आसान है लेकिन सूची अपने प्रत्येक तत्व के सटीक प्रकार को "याद" नहीं करेगी। इसके बजाय यह प्रत्येक तत्व को एक सामान्य सुपर प्रकार T पर उजागर करेगा। String और IntAny के बीच अधिक सटीक सुपर सुपर प्रकार, सूची का प्रकार List[Any] है।

समस्या यह है कि: क्या भूलना है और क्या याद रखना है? हम तत्वों के सटीक प्रकार को भूलना चाहते हैं लेकिन हम याद रखना चाहते हैं कि वे Show के सभी उदाहरण हैं। निम्नलिखित वर्ग करता है वास्तव में

abstract class Ex[TC[_]] { 
    type t 
    val value : t 
    implicit val instance : TC[t] 
} 

implicit def ex[TC[_], A](a : A)(implicit A : TC[A]) = new Ex[TC] { 
    type t = A 
    val value = a 
    val instance = A 
} 

यह अस्तित्व का एक एन्कोडिंग है:

val ex_i : Ex[Show] = ex[Show, Int](i) 
val ex_s : Ex[Show] = ex[Show, String](s) 

यह इसी प्रकार वर्ग उदाहरण के साथ एक मूल्य पैक।

अंत में हम के लिए Ex[Show]

implicit val exShow = new Show[Ex[Show]] { 
    def show(e : Ex[Show]) : String = { 
    import e._ 
    e.value.show 
    } 
} 

import e._ एक उदाहरण जोड़ने के दायरे में लाने के लिए उदाहरण के लिए आवश्यक है सकते हैं। Implicits के जादू के लिए धन्यवाद:

val list = List[Ex[Show]](i , s) 
for (e <- list) yield e.show 

जो अपेक्षित कोड के बहुत करीब है।