2015-03-10 5 views
10

टाइपक्लास को परिभाषित करते समय, टाइपक्लास की परिभाषा में फ़ंक्शन को शामिल/बहिष्कृत करने के बीच आप कैसे निर्णय लेते हैं? उदाहरण के लिए, इन 2 मामलों के बीच मतभेदों को क्या कर रहे हैं:टाइपक्लास: डिफॉल्ट कार्यान्वयन बनाम अलग फ़ंक्शन

class Graph g where 
    ... 

    insertNode :: g -> Node -> g 
    insertNode graph node = ... 

बनाम

class Graph g where 
    ... 

insertNode :: (Graph g) => g -> Node -> g 
insertNode graph node = ... 

उत्तर

9

मुझे लगता है कि यहां तनाव में कुछ तत्व हैं। सामान्य विचार है कि वर्ग परिभाषाओं को न्यूनतम होना चाहिए, और केवल स्वतंत्र फ़ंक्शंस होना चाहिए।bhelkir के जवाब बताते हैं, अपने वर्ग कार्यों a, b और c, लेकिन ca और b के मामले में लागू किया जा सकता का समर्थन करता है तो उस वर्ग के c बाहर परिभाषित करने के लिए एक तर्क है।

लेकिन यह सामान्य विचार कुछ अन्य विरोधाभासी मुद्दों में चलता है।

सबसे पहले, अक्सर एक से अधिक न्यूनतम संचालन सेट होते हैं जो समान कक्षा को समान रूप से परिभाषित कर सकते हैं। हास्केल में Monad की क्लासिक परिभाषा इस (एक सा साफ) है:

class Monad m where 
    return :: a -> m a 
    (>>=) :: m a -> (a -> m b) -> m b 

लेकिन यह अच्छी तरह से जाना जाता है वहां वैकल्पिक परिभाषा, की तरह हैं कि यह एक:

class Applicative m => Monad m where 
    join :: m (m a) -> m a 

return और >>= के लिए पर्याप्त हैं join लागू करें, लेकिन fmap, pure और join>>= को लागू करने के लिए भी पर्याप्त हैं।

Applicative के साथ एक समान चीज़। यह विहित हास्केल परिभाषा है:

class Functor f => Applicative f where 
    pure :: a -> f a 
    (<*>) :: f (a -> b) -> f a -> f b 

लेकिन इनमें से कोई भी बराबर है:

class Functor f => Applicative f where 
    unit :: f() 
    (<*>) :: f (a -> b) -> f a -> f b 

class Functor f => Applicative f where 
    pure :: a -> f a 
    fpair :: f a -> f b -> f (a, b) 

class Functor f => Applicative f where 
    unit :: f() 
    fpair :: f a -> f b -> f (a, b) 

class Functor f => Applicative f where 
    unit :: f() 
    liftA2 :: (a -> b -> c) -> f a -> f b -> f c 

इन वर्ग परिभाषाओं में से किसी को देखते हुए, आप एक के रूप में दूसरों से किसी में से किसी भी विधि लिख सकते हैं वर्ग के बाहर व्युत्पन्न कार्य। पहला क्यों चुना गया था? मैं आधिकारिक रूप से जवाब नहीं दे सकता, लेकिन मुझे लगता है कि यह हमें तीसरे बिंदु पर लाता है: प्रदर्शन विचारfpair उनमें से कई में f a और f b मूल्यों को टुपल्स बनाकर जोड़ता है, लेकिन Applicative कक्षा के अधिकांश उपयोगों के लिए हम वास्तव में उन tuples को नहीं चाहते हैं, हम सिर्फ f a और f b से खींचे गए मानों को जोड़ना चाहते हैं; कैननिकल परिभाषा हमें यह चुनने की अनुमति देती है कि इस संयोजन को किस संयोजन के साथ किया जाए।

एक अन्य प्रदर्शन विचार यह है कि यदि कक्षा में कुछ विधियां दूसरों के संदर्भ में निश्चित हो सकती हैं, तो ये सामान्य परिभाषा कक्षा के सभी उदाहरणों के लिए अनुकूल नहीं हो सकती है। यदि हम उदाहरण के रूप में Foldable लेते हैं, foldMap और foldr अंतर-भिन्न हैं, लेकिन कुछ प्रकार दूसरे की तुलना में एक और अधिक कुशलता से समर्थन करते हैं। अक्सर हमारे पास गैर-न्यूनतम श्रेणी परिभाषाएं होती हैं ताकि उदाहरणों को विधियों के अनुकूलित कार्यान्वयन प्रदान किए जा सकें।

+1

मामूली क्विबल: '<,>' कानूनी ऑपरेटर नाम नहीं है। –

+0

@ ØrjanJohansen: डी ओह! मैंने इसे 'fpair' में बदल दिया (नाम बनाया,' fmap' के समानता का थोड़ा सा)। –

+0

न्यूनतम कक्षाओं को न्यूनतम इंटरफ़ेस के आधार पर न्यूनतम परिवर्धन के लिए डिफ़ॉल्ट कार्यान्वयन के दौरान न्यूनतम वर्गों को रखने का क्या फायदा है? – timdiels

7

एक typeclass की परिभाषा में एक समारोह भी शामिल है मतलब है कि यह अधिरोहित जा सकता है। इस मामले में, आपको Graph टाइपक्लास के अंदर होने की आवश्यकता होगी क्योंकि यह Graph g => g देता है, और Graph के प्रत्येक विशिष्ट उदाहरण को यह मानना ​​होगा कि उस मान को कैसे बनाया जाए। वैकल्पिक रूप से, आप प्रकार Graph g => g के मानों के निर्माण के उद्देश्य के लिए टाइपक्लास में एक फ़ंक्शन निर्दिष्ट कर सकते हैं और फिर insertNode इसके परिणामस्वरूप उस फ़ंक्शन का उपयोग कर सकते हैं।

टाइपक्लास के बाहर एक फ़ंक्शन को रखने का अर्थ है कि इसे संशोधित नहीं किया जा सकता है, बल्कि यह भी कि कक्षा को अव्यवस्थित नहीं करता है। उदाहरण के रूप में mapM फ़ंक्शन पर विचार करें। Monad कक्षा में होने की कोई आवश्यकता नहीं है, और शायद आप नहीं चाहते हैं कि लोग mapM के अपने कार्यान्वयन लिख रहे हों, इसे सभी संदर्भों में एक ही चीज़ करना चाहिए। एक और उदाहरण के रूप में, Num typeclass का हिस्सा नहीं होना चाहिए समारोह

-- f(x) = 1 + 3x^2 - 5x^3 + 10x^4 
aPoly :: Num a => a -> a 
aPoly x = 1 + 3 * x * x - 5 * x * x * x + 10 * x * x * x * x 

जाहिर aPoly पर विचार, यह सिर्फ एक यादृच्छिक समारोह है कि Num तरीकों का उपयोग करने के लिए होता है। इसका Num होने का अर्थ क्या है इसका कोई लेना-देना नहीं है।

वास्तव में, यह डिज़ाइन के लिए नीचे आता है। फ़ंक्शंस आमतौर पर टाइपक्लास के भीतर निर्दिष्ट होते हैं यदि वे उस टाइपक्लास का उदाहरण होने के लिए अभिन्न हैं। कभी-कभी कार्यों को टाइपक्लास में शामिल किया जाता है लेकिन एक डिफ़ॉल्ट परिभाषा के साथ ताकि एक विशिष्ट प्रकार इसे अधिक कुशल बनाने के लिए इसे अधिभारित कर सके, लेकिन अधिकांश भाग के लिए कक्षा के सदस्यों को कम से कम रखने के लिए समझदारी होती है। इसे देखने का एक तरीका सवाल पूछकर है "क्या यह कार्य केवल वर्ग की बाधा के साथ लागू किया जा सकता है?" अगर जवाब नहीं है, तो यह कक्षा में होना चाहिए। यदि उत्तर हाँ है, तो उस समय का विशाल बहुमत इसका मतलब है कि समारोह कक्षा के बाहर ले जाया जाना चाहिए। केवल तभी जब अधिभार में सक्षम होने से प्राप्त मूल्य प्राप्त होता है, इसे कक्षा में स्थानांतरित किया जाना चाहिए। यदि उस फ़ंक्शन को अधिभारित करने से दूसरे कोड को तोड़ दिया जा सकता है जो किसी विशेष तरीके से व्यवहार करने की अपेक्षा करता है, तो इसे ओवरलोड नहीं किया जाना चाहिए।

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

class Num a where 
    (+) :: a -> a -> a 
    (*) :: a -> a -> a 
    (-) :: a -> a -> a 
    a - b = a + negate b 
    negate :: a -> a 
    negate a = 0 - a 
    abs :: a -> a 
    signum :: a -> a 
    fromInteger :: Integer -> a 

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

+0

किसी फ़ंक्शन को ओवरलोड करना गलत तरीका हमेशा कोड तोड़ देगा। तुम क्या कहना चाहते हो? – dfeuer

संबंधित मुद्दे