2010-09-16 12 views
10

हास्केल में बड़े कार्यक्रम लिखते समय, मुझे आमतौर पर एक समस्या में चल रहा है। मुझे लगता है कि मैं अक्सर कई अलग-अलग प्रकारों को चाहता हूं जो आंतरिक प्रतिनिधित्व और कई मूल संचालन साझा करते हैं।एक ही आंतरिक प्रतिनिधित्व और न्यूनतम बॉयलरप्लेट के साथ एकाधिक प्रकारों को संभालना?

इस समस्या को हल करने के लिए दो अपेक्षाकृत स्पष्ट दृष्टिकोण हैं।

कोई एक प्रकार का वर्ग और GeneralizedNewtypeDeriving एक्सटेंशन का उपयोग कर रहा है। उपयोग केस इच्छाओं के साझा संचालन का समर्थन करने के लिए एक प्रकार के वर्ग में पर्याप्त तर्क रखें। वांछित प्रतिनिधित्व के साथ एक प्रकार बनाएँ, और उस प्रकार के लिए प्रकार वर्ग का एक उदाहरण बनाएँ। फिर, प्रत्येक उपयोग के मामले के लिए, नए प्रकार के साथ इसके लिए रैपर बनाएं, और सामान्य वर्ग प्राप्त करें।

दूसरा एक प्रकार का फैंटम प्रकार परिवर्तक के साथ घोषित करना है, और उसके बाद EmptyDataDecls का उपयोग प्रत्येक अलग-अलग उपयोग केस के लिए अलग-अलग प्रकार बनाने के लिए करें।

मेरी मुख्य चिंता आंतरिक प्रतिनिधित्व और संचालन साझा करने वाले मानों को मिश्रित नहीं कर रही है, लेकिन मेरे कोड में अलग-अलग अर्थ हैं। उन दोनों दृष्टिकोणों ने उस समस्या को हल किया है, लेकिन काफी बेकार महसूस करते हैं। मेरी दूसरी चिंता बॉयलरप्लेट की मात्रा को कम कर रही है, और दोनों दृष्टिकोण उस पर पर्याप्त प्रदर्शन करते हैं।

प्रत्येक दृष्टिकोण के फायदे और नुकसान क्या हैं? क्या कोई ऐसी तकनीक है जो मैं चाहता हूं कि वह मुझे करने के करीब आती है, बॉयलरप्लेट कोड के बिना टाइप सुरक्षा प्रदान करती है?

उत्तर

2

मैंने खिलौनों के उदाहरणों को बेंचमार्क किया है और दोनों दृष्टिकोणों के बीच प्रदर्शन अंतर नहीं मिला है, लेकिन उपयोग आमतौर पर थोड़ा अलग होता है।

उदाहरण के लिए, कुछ मामलों में आपके पास एक सामान्य प्रकार है जिसका कन्स्ट्रक्टर उजागर होता है और आप newtype रैपर का उपयोग अधिक से अधिक विशिष्ट प्रकार के संकेत के लिए करना चाहते हैं। newtype रों का उपयोग करना तो जैसी साइटों फोन की ओर जाता है,

s1 = Specific1 $ General "Bob" 23 
s2 = Specific2 $ General "Joe" 19 

तथ्य यह है कि आंतरिक अभ्यावेदन विभिन्न विशिष्ट newtypes के बीच एक ही हैं कहाँ पारदर्शी है।

प्रकार टैग दृष्टिकोण लगभग हमेशा प्रतिनिधित्व निर्माता छिपने के साथ चला जाता,

data General2 a = General2 String Int 

और स्मार्ट कंस्ट्रक्टर्स के उपयोग के लिए, डेटा प्रकार परिभाषा के लिए अग्रणी और जैसी साइटों फोन,

mkSpecific1 "Bob" 23 

भाग इस कारण से कि आप यह संकेत देने के कुछ वाक्य रचनात्मक तरीके से प्रकाश चाहते हैं कि आप कौन सा टैग चाहते हैं। यदि आपने स्मार्ट कन्स्ट्रक्टर प्रदान नहीं किए हैं, तो क्लाइंट कोड अक्सर चीजों को संकीर्ण करने के लिए प्रकार एनोटेशन उठाएगा, उदाहरण के लिए,

myValue = General2 String Int :: General2 Specific1 

एक बार जब आप स्मार्ट कन्स्ट्रक्टर को अपनाते हैं, तो आप आसानी से टैग के दुरुपयोग को पकड़ने के लिए अतिरिक्त सत्यापन तर्क जोड़ सकते हैं। प्रेत प्रकार दृष्टिकोण का एक अच्छा पहलू यह है कि पैटर्न मिलान मिलान आंतरिक कोड के लिए बिल्कुल नहीं बदला गया है जिसका प्रतिनिधित्व तक पहुंच है।

internalFun :: General2 a -> General2 a -> Int 
internalFun (General2 _ age1) (General2 _ age2) = age1 + age2 

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

+0

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

+1

@camccann ghc-core और मानदंड की सुंदरता स्मृति पूरक के लिए अनुभवजन्य सबूत है! :) मुझे लगता है कि प्रदर्शन प्रश्न के साथ और अधिक करना है कि कक्षा से आने वाले संचालन मूल्य के रनटाइम प्रतिनिधित्व के विपरीत उनके प्रदर्शन को प्रभावित करते हैं या नहीं। पॉलिमॉर्फिक फ़ंक्शन '(सामान्य ए, सामान्य बी) => ए -> बी -> Int' से' सामान्य 2 ए -> सामान्य 2 बी -> Int' से जाते हैं। – Anthony

3

एक और सीधा दृष्टिकोण है।

data MyGenType = Foo | Bar 

op :: MyGenType -> MyGenType 
op x = ... 

op2 :: MyGenType -> MyGenType -> MyGenType 
op2 x y = ... 

newtype MySpecialType {unMySpecial :: MyGenType} 

inMySpecial f = MySpecialType . f . unMySpecial 
inMySpecial2 f x y = ... 

somefun = ... inMySpecial op x ... 
someOtherFun = ... inMySpecial2 op2 x y ... 

वैकल्पिक रूप से,

newtype MySpecial a = MySpecial a 
instance Functor MySpecial where... 
instance Applicative MySpecial where... 

somefun = ... fmap op x ... 
someOtherFun = ... liftA2 op2 x y ... 

मुझे लगता है कि आप किसी भी आवृत्ति के साथ अपने सामान्य प्रकार "नग्न" उपयोग करना चाहते हैं इन तरीकों अच्छे हैं, और केवल कभी कभी यह टैग करना चाहते हैं। यदि, दूसरी तरफ, आप आमतौर पर इसे टैग का उपयोग करना चाहते हैं, तो प्रेत प्रकार दृष्टिकोण अधिक सीधे व्यक्त करता है जो आप चाहते हैं।

+0

वास्तव में उस शिफ्ट कुंजी को वास्तव में पसंद करते हैं, हुह? –

+0

ओह। स्वरूपण तय – sclv

+1

बीटीडब्ल्यू, आप टेम्पलेट हैकेल के साथ MySpecial1,2,3 ../ withMySpecial आदि में स्वतः उत्पन्न कर सकते हैं। http://github.com/yairchu/peakachu/blob/master/src/Data/Newtype.hs – yairchu

1

उपयोग केस इच्छाओं के साझा संचालन का समर्थन करने के लिए एक प्रकार के वर्ग में पर्याप्त तर्क रखें। वांछित प्रतिनिधित्व के साथ एक प्रकार बनाएँ, और उस प्रकार के लिए प्रकार वर्ग का एक उदाहरण बनाएँ। फिर, प्रत्येक उपयोग के मामले के लिए, नए प्रकार के साथ इसके लिए रैपर बनाएं, और सामान्य वर्ग प्राप्त करें।

यह प्रकार की प्रकृति और किस तरह के संचालन शामिल हैं, के आधार पर कुछ नुकसान प्रस्तुत करता है।

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

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

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

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