उम्मीद है कि अधिक अनुभव के साथ किसी के लिए एक अधिक पॉलिश, लड़ाई-परीक्षण किया और तैयार जवाब होगा, लेकिन यहाँ यह मेरी शॉट है।
आप अपने पाई है और GADTs साथ अपेक्षाकृत कम कीमत पर भी इसका हिस्सा खा सकते हैं: बातें हम बदल दिया है यहाँ
{-# LANGUAGE GADTs #-}
data P0 -- phase zero
data P1 -- phase one
data Type p where
TypeInt :: Id -> Type p
TypePointer :: Id -> Type p -> Type p -- target type
TypeArray :: Id -> Type p -> Expr p -> Type p -- elt type, elt count
TypeStruct :: Id -> [(String, Type p)] -> Type p -- [(field name, field type)]
TypeOf :: Id -> Expr P0 -> Type P0
TypeDef :: Id -> Type P0 -> Type P0
data Expr p where
ExprInt :: Int -> Expr p -- literal int
ExprVar :: Var -> Expr p -- variable
ExprSizeof :: Type P0 -> Expr P0
ExprUnop :: Unop -> Expr p -> Expr p
ExprBinop :: Binop -> Expr p -> Expr p -> Expr p
ExprField :: Bool -> Expr P0 -> String -> Expr P0 -- Bool gives true=>s.field, false=>p->field
हैं:
डेटाटाइप्स अब GADT का उपयोग वाक्य - विन्यास। इसका मतलब है कि रचनाकारों को उनके प्रकार के हस्ताक्षरों का उपयोग करके घोषित किया जाता है। data Foo = Bar Int Char
data Foo where Bar :: Int -> Char -> Foo
बन जाता है (वाक्यविन्यास से अलग, दोनों पूरी तरह से समकक्ष हैं)।
हमने Type
और Expr
दोनों में एक प्रकार चर जोड़ा है। यह एक तथाकथित प्रेत प्रकार चर है: p
प्रकार का कोई वास्तविक डेटा संग्रहित नहीं है, इसका उपयोग केवल प्रकार सिस्टम में इनवेरिएंट को लागू करने के लिए किया जाता है। चरण शून्य और चरण एक:
हम पहले और परिवर्तन के बाद के चरणों का प्रतिनिधित्व करने के डमी प्रकार घोषित कर दिया है। (कई चरणों के साथ एक और विस्तृत प्रणाली में हम उन्हें इंगित करने के लिए संभावित रूप से टाइप-स्तरीय संख्याओं का उपयोग कर सकते हैं।)
जीएडीटी हमें डेटा संरचना में टाइप-स्तरीय इनवेरिएंट स्टोर करने देते हैं। यहां हमारे पास दो हैं। पहला यह है कि पुनरावर्ती पदों को उसी चरण का होना चाहिए, जिसमें संरचना शामिल है। उदाहरण के लिए, TypePointer :: Id -> Type p -> Type p
पर, आप TypePointer
कन्स्ट्रक्टर को पास करते हैं और परिणामस्वरूप Type p
प्राप्त करते हैं, और p
एस एक ही प्रकार का होना चाहिए। (अगर हम विभिन्न प्रकार की अनुमति के लिए चाहते थे, हम p
और q
इस्तेमाल कर सकते हैं।)
पीछे नहीं है कि हम इस तथ्य है कि कुछ निर्माताओं के केवल पहले चरण में इस्तेमाल किया जा सकता लागू। अधिकांश रचनाकार प्रेत प्रकार परिवर्तनीय p
में पॉलिमॉर्फिक हैं, लेकिन उनमें से कुछ की आवश्यकता है कि यह P0
हो। इसका मतलब है कि उन रचनाकारों का उपयोग केवल Type P0
या Expr P0
के मानों के निर्माण के लिए किया जा सकता है, किसी भी अन्य चरण में नहीं।
जीएडीटी दो दिशाओं में काम करते हैं। पहला यह है कि यदि आपके पास कोई ऐसा फ़ंक्शन है जो Type P1
देता है, और इसे बनाने के लिए Type P0
लौटने वाले रचनाकारों में से एक का उपयोग करने का प्रयास करें, तो आपको एक प्रकार की त्रुटि मिल जाएगी। इसे "निर्माण द्वारा सही" कहा जाता है: यह अमान्य संरचना बनाने के लिए स्थिर रूप से असंभव है (बशर्ते आप टाइप सिस्टम में सभी प्रासंगिक आविष्कारों को एन्कोड कर सकें)। इसका फ्लिप पक्ष यह है कि यदि आपके पास Type P1
का मान है, तो आप सुनिश्चित कर सकते हैं कि यह सही ढंग से बनाया गया था: TypeOf
और TypeDef
रचनाकारों का उपयोग नहीं किया जा सकता है (वास्तव में, यदि आप पैटर्न मिलान करने का प्रयास करते हैं तो संकलक शिकायत करेगा उन पर), और किसी भी रिकर्सिव पदों को चरण P1
का होना चाहिए। अनिवार्य रूप से, जब आप एक जीएडीटी बनाते हैं तो आप सबूत जमा करते हैं कि प्रकार की बाधाएं संतुष्ट होती हैं, और जब आप पैटर्न पर मेल खाते हैं तो आप उस सबूत को पुनः प्राप्त करते हैं और इसका लाभ उठा सकते हैं।
यह आसान हिस्सा था। दुर्भाग्यवश, हमारे पास दो प्रकार के बीच कुछ मतभेद हैं जो कि रचनाकारों की अनुमति है: कुछ कन्स्ट्रक्टर तर्क चरणों के बीच अलग हैं, और कुछ केवल परिवर्तन-परिवर्तन चरण में मौजूद हैं। हम फिर से एन्कोड करने के लिए जीएडीटी का उपयोग कर सकते हैं, लेकिन यह कम लागत और सुरुचिपूर्ण नहीं है। एक समाधान उन सभी रचनाकारों को डुप्लिकेट करना होगा जो अलग-अलग हैं, और P0
और P1
के लिए प्रत्येक एक है। लेकिन नकल अच्छा नहीं है। हम ऐसा करने के लिए कोशिश कर सकते हैं यह अधिक परिष्कृत:
-- a couple of helper types
-- here I take advantage of the fact that of the things only present in one phase,
-- they're always present in P1 and not P0, and not vice versa
data MaybeP p a where
NothingP :: MaybeP P0 a
JustP :: a -> MaybeP P1 a
data EitherP p a b where
LeftP :: a -> EitherP P0 a b
RightP :: b -> EitherP P1 a b
data Type p where
TypeInt :: Id -> Type p
TypePointer :: Id -> Type p -> Type p
TypeArray :: Id -> Type p -> EitherP p (Expr p) Int -> Type p
TypeStruct :: Id -> [(String, MaybeP p Int, Type p)] -> Type p
TypeOf :: Id -> Expr P0 -> Type P0
TypeDef :: Id -> Type P0 -> Type P0
-- for brevity
type MaybeType p = MaybeP p (Type p)
data Expr p where
ExprInt :: MaybeType p -> Int -> Expr p
ExprVar :: MaybeType p -> Var -> Expr p
ExprSizeof :: Type P0 -> Expr P0
ExprUnop :: MaybeType p -> Unop -> Expr p -> Expr p
ExprBinop :: MaybeType p -> Binop -> Expr p -> Expr p -> Expr p
ExprField :: Bool -> Expr P0 -> String -> Expr P0
यहाँ कुछ सहायक प्रकार हम तथ्य यह है कि कुछ निर्माता तर्क केवल चरण एक (
MaybeP
) में मौजूद हो सकता है लागू किया गया है के साथ
और कुछ दोनों के बीच अलग हैं चरण (EitherP
)। हालांकि यह हमें पूरी तरह से टाइप-सुरक्षित बनाता है, यह थोड़ा सा विज्ञापन लगता है और हमें अभी भी MaybeP
एस और EitherP
एस के अंदर और बाहर चीज़ों को लपेटना होगा। मुझे नहीं पता कि उस सम्मान में एक बेहतर समाधान है या नहीं। पूर्ण प्रकार की सुरक्षा कुछ है, हालांकि: हम fromJustP :: MaybeP P1 a -> a
लिख सकते हैं और यह सुनिश्चित कर लें कि यह पूरी तरह से और पूरी तरह से सुरक्षित है।
अद्यतन: एक वैकल्पिक उपयोग करने के लिए है TypeFamilies
:
data Proxy a = Proxy
class Phase p where
type MaybeP p a
type EitherP p a b
maybeP :: Proxy p -> MaybeP p a -> Maybe a
eitherP :: Proxy p -> EitherP p a b -> Either a b
phase :: Proxy p
phase = Proxy
instance Phase P0 where
type MaybeP P0 a =()
type EitherP P0 a b = a
maybeP _ _ = Nothing
eitherP _ a = Left a
instance Phase P1 where
type MaybeP P1 a = a
type EitherP P1 a b = b
maybeP _ a = Just a
eitherP _ a = Right a
Expr
और Type
रिश्तेदार पिछले संस्करण है कि निर्माताओं, उदा एक अतिरिक्त Phase p
बाधा की आवश्यकता है के लिए एक ही परिवर्तन ExprInt :: Phase p => MaybeType p -> Int -> Expr p
।
यहाँ अगर एक Type
या Expr
में p
के प्रकार में जाना जाता है, आप स्थिर पता कर सकते हैं MaybeP
रों ()
हो जाएगा कि क्या है या दिए गए प्रकार और प्रकार EitherP
रों हैं जो, और उन्हें लगता है कि के रूप में सीधे उपयोग कर सकते हैं स्पष्ट unwrapping के बिना टाइप करें। जब p
अज्ञात है तो आप Phase
कक्षा से maybeP
और eitherP
का उपयोग करके पता लगा सकते हैं कि वे क्या हैं। (Proxy
तर्क आवश्यक हैं, अन्यथा संकलक के पास आपको कौन सा चरण बताए जाने का कोई तरीका नहीं होगा।) यह GADT संस्करण के समान है, यदि p
ज्ञात है, तो आप सुनिश्चित कर सकते हैं कि MaybeP
और EitherP
में, जबकि अन्यथा आपको पैटर्न दोनों संभावनाओं से मेल खाना पड़ेगा। यह समाधान या तो सम्मान में सही नहीं है कि 'लापता' तर्क पूरी तरह से गायब होने की बजाय ()
बन जाते हैं।
निर्माण Expr
और Type
रों भी मोटे तौर पर दो संस्करणों के बीच समान लगता है: यदि मान आप का निर्माण कर रहे हैं कुछ भी चरण-विशिष्ट इसके बारे में है तो यह अपने प्रकार में है कि चरण निर्दिष्ट करना होगा। समस्या तब आती है जब आप p
में फ़ंक्शन पॉलिमॉर्फिक लिखना चाहते हैं लेकिन फिर भी चरण-विशिष्ट भागों को संभालना चाहते हैं। GADTs के साथ इस स्पष्ट है:
asdf :: MaybeP p a -> MaybeP p a
asdf NothingP = NothingP
asdf (JustP a) = JustP a
ध्यान दें कि अगर मैं केवल asdf _ = NothingP
लिखा था संकलक, शिकायत की है जाएगा क्योंकि उत्पादन के प्रकार के इनपुट के रूप में ही होने की गारंटी नहीं दी जाएगी। पैटर्न मिलान से हम यह पता लगा सकते हैं कि इनपुट किस प्रकार का था और उसी प्रकार के परिणाम को वापस कर दिया।
TypeFamilies
संस्करण के साथ, हालांकि, यह बहुत कठिन है। बस maybeP
और परिणामी Maybe
का उपयोग करके आप कंपाइलर को कुछ भी साबित नहीं कर सकते हैं। इसके बजाय आप maybeP
और eitherP
वापसी Maybe
और Either
होने के, वहाँ से जिस तरह का हिस्सा प्राप्त कर सकते हैं, उन्हें maybe
और either
की तरह कार्य करता है जो यह भी एक प्रकार समानता उपलब्ध बनाने Deconstructor बनाने:
maybeP :: Proxy p -> (p ~ P0 => r) -> (p ~ P1 => a -> r) -> MaybeP p a -> r
eitherP :: Proxy p -> (p ~ P0 => a -> r) -> (p ~ P1 => b -> r) -> EitherP p a b -> r
(ध्यान दें कि हम Rank2Types
की जरूरत । इस, और टिप्पणी भी है कि इन अनिवार्य रूप से MaybeP
और EitherP
की GADT संस्करणों में से सीपीएस-बदल संस्करण हैं) के लिए
तो हम लिख सकते हैं:
asdf :: Phase p => MaybeP p a -> MaybeP p a
asdf a = maybeP phase() id a
लेकिन वह पर्याप्त नहीं अभी भी है, क्योंकि GHC का कहना है:
data.hs:116:29:
Could not deduce (MaybeP p a ~ MaybeP p0 a0)
from the context (Phase p)
bound by the type signature for
asdf :: Phase p => MaybeP p a -> MaybeP p a
at data.hs:116:1-29
NB: `MaybeP' is a type function, and may not be injective
In the fourth argument of `maybeP', namely `a'
In the expression: maybeP phase() id a
In an equation for `asdf': asdf a = maybeP phase() id a
शायद तुम कहीं एक प्रकार हस्ताक्षर के साथ इस का समाधान कर सकता है, लेकिन उस बिंदु पर इस तरह की और परेशान तुलना में यह लायक है लगता है। इसलिए किसी और से आगे की जानकारी लंबित है, मैं थोड़ा सा वाक्य रचनात्मक शोर की कीमत पर, सरल और अधिक मजबूत होने के नाते, जीएडीटी संस्करण का उपयोग करने की सिफारिश करने जा रहा हूं। फिर
अद्यतन: समस्या यहाँ क्योंकि MaybeP p a
एक प्रकार समारोह है और कोई अन्य जानकारी के अनुसार जाना है, GHC कोई रास्ता नहीं पता है कि p
और a
होना चाहिए है कि था। अगर मैं Proxy p
में पास करता हूं और phase
के बजाय इसका उपयोग करता हूं जो p
हल करता है, लेकिन a
अभी भी अज्ञात है।
मुझे एक ही समस्या है।यह एक एकीकृत संरचना के बारे में एक अच्छा विचार लगता है, हालांकि, अगर आपने किया, तो क्या आप वास्तव में दोनों चरणों के लिए एक ही कार्य का पुन: उपयोग कर सकते हैं? ऐसा लगता है कि छोटे संरचनात्मक परिवर्तनों के परिणामस्वरूप तर्क की बड़ी छलांग होती है। –
हां, यद्यपि यदि इन प्रकारों पर चलने वाले प्रत्येक फ़ंक्शन एक मोनैड में चलता है जो आपको किसी संदर्भ तक पहुंच प्रदान करता है, और त्रुटियों को उत्पन्न करने की क्षमता देता है, तो पुरानी संरचनाओं को नए लोगों में बदलने के लिए संभव होना चाहिए। – pat