Monoid
के बारे में आपका विचार एक अच्छी शुरुआत है, लेकिन Monoid
काफी सामान्य नहीं है। आपको वास्तव में क्या चाहिए Category
है। आप नौकरी के लिए एक कस्टम बना सकते हैं, लेकिन आप केवल एक निश्चित प्रकार के आंशिक रिकॉर्ड का प्रतिनिधित्व करने के विचार को छोड़कर श्रेणियों के बारे में सोचने से बच सकते हैं और इसके बजाय अलग-अलग क्षेत्रों के आंशिक रिकॉर्ड अलग-अलग प्रकार के होते हैं। इसका मतलब है कि आप प्रकार और कार्यों की श्रेणी के साथ काम कर रहे हैं, कभी-कभी हास्क कहा जाता है, लेकिन आपको इसके बारे में सोचना नहीं है। चेतावनी: हैकेज पर फैंसी रिकॉर्ड पैकेजों में से एक शायद इस तरह की चीज को करने में आसान बनाता है, लेकिन मैं उनमें से किसी को भी उपयोग करने के लिए पर्याप्त रूप से अच्छी तरह से समझ नहीं पा रहा हूं, अकेले उन्हें सलाह दें।
पहले बॉयलर-प्लेट
{-# LANGUAGE TypeFamilies #-}
{-# LANGUAGE TypeOperators #-}
{-# LANGUAGE GADTs #-}
{-# LANGUAGE DataKinds #-}
{-# LANGUAGE MultiParamTypeClasses #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE FlexibleInstances #-}
module PartialRec where
import Data.Proxy
अब प्रकार के। PList
एक (आंशिक) रिकॉर्ड का प्रतिनिधित्व करता है। इसकी पहली तर्क सूची रिकॉर्ड फ़ील्ड के प्रकारों का प्रतिनिधित्व करती है, जबकि दूसरी तर्क सूची इंगित करती है कि कौन से फ़ील्ड मौजूद हैं।
-- Skip is totally unnecessary, but makes the
-- syntax of skips a bit less horrible.
data Skip = Skip
infixr 6 `PCons`, `PSkip`
data PList :: [*] -> [Bool] -> * where
PNil :: PList '[] '[]
PCons :: a -> PList as bs -> PList (a ': as) ('True ': bs)
PSkip :: Skip -> PList as bs -> PList (a ': as) ('False ': bs)
हम व्यक्त करने के लिए कैसे प्रकार गठबंधन जब दो आंशिक रिकॉर्ड जोड़ दिया जाता है एक प्रकार परिवार का उपयोग करें। विशेष रूप से, परिणामस्वरूप किसी भी आंशिक रिकॉर्ड में मौजूद कोई भी क्षेत्र उपस्थित होगा।
type family Combine (as :: [Bool]) (bs :: [Bool]) :: [Bool] where
Combine '[] '[] = '[]
Combine ('True ': xs) (y ': ys) = 'True ': Combine xs ys
Combine ('False ': xs) (y ': ys) = y ': Combine xs ys
combine
समारोह में एक ही क्षेत्र प्रकार के और किसी भी या तो आंशिक रिकॉर्ड में मौजूद क्षेत्रों के साथ एक नया एक बनाने के लिए दो आंशिक रिकॉर्ड को जोड़ती है। यदि दोनों रिकॉर्ड दोनों में मौजूद है, तो पहला चुना जाता है।
combine :: PList as bs -> PList as cs -> PList as (Combine bs cs)
combine PNil PNil = PNil
combine (PCons x xs) (PSkip _ ys) = PCons x (combine xs ys)
combine (PSkip _ xs) (PCons y ys) = PCons y (combine xs ys)
combine (PSkip _ xs) (PSkip _ ys) = PSkip Skip (combine xs ys)
combine (PCons x xs) (PCons _ ys) = PCons x (combine xs ys)
डिफ़ॉल्ट तर्क buildRec
पर छोड़ दिया गया है। buildRec
फ़ील्ड के पर्याप्त सेट के साथ आंशिक रिकॉर्ड लेता है, और आवश्यक फ़ील्ड और वास्तव में मौजूद किसी भी वैकल्पिक फ़ील्ड के आधार पर वैकल्पिक फ़ील्ड के मानों का उत्पादन करता है। buildRec
वास्तव में कई प्रकार के फ़ील्ड सेटों का समर्थन करने के लिए, एक प्रकार के परिवार द्वारा चुने गए उदाहरणों के साथ एक प्रकार के वर्ग का उपयोग करके कार्यान्वित किया जाता है।
-- Names for instances
data BuilderTag = Builder1 | Builder2
-- Given a list of types present, determines
-- the correct Builder instance to use.
type family ChooseBuilder (present :: [Bool]) :: BuilderTag where
ChooseBuilder '[ 'True, 'True, 'True, b3 ] = Builder2
ChooseBuilder '[ 'True, b1, 'True, b2 ] = Builder1
class Builder (tag :: BuilderTag) (present :: [Bool]) where
buildRec' :: proxy tag -> PList '[Int, Char, Bool, Integer] present -> (Int, Char, Bool, Integer)
buildRec :: forall tag present . (Builder tag present, tag ~ ChooseBuilder present)
=> PList '[Int, Char, Bool, Integer] present -> (Int, Char, Bool, Integer)
buildRec xs = buildRec' (Proxy :: Proxy tag) xs
instance Builder 'Builder1 '[ 'True, b1, 'True, b2 ] where
buildRec' _ (i `PCons` Skip `PSkip` b `PCons` Skip `PSkip` PNil) = (i, toEnum (i + fromEnum b) , b, if i > 3 && b then 12 else 13)
buildRec' _ (i `PCons` Skip `PSkip` b `PCons` intg `PCons` PNil) = (i, toEnum (i + fromEnum b + fromIntegral intg), b, intg)
buildRec' _ (i `PCons` c `PCons` b `PCons` Skip `PSkip` PNil) = (i, c, b, fromIntegral i)
buildRec' _ (i `PCons` c `PCons` b `PCons` intg `PCons` PNil) = (i, c, b, intg)
instance Builder 'Builder2 '[ 'True, 'True, 'True, b3 ] where
buildRec' _ (i `PCons` c `PCons` b `PCons` Skip `PSkip` PNil) = (i, c, b, fromIntegral i)
buildRec' _ (i `PCons` c `PCons` b `PCons` intg `PCons` PNil) = (i, c, b, intg)
यहां प्रत्येक फ़ील्ड के साथ आंशिक रिकॉर्ड बनाने के लिए कुछ फ़ंक्शन हैं।
justInt :: Int -> PList '[Int, a, b, c] '[ 'True, 'False, 'False, 'False]
justInt x = x `PCons` Skip `PSkip` Skip `PSkip` Skip `PSkip` PNil
justChar :: Char -> PList '[a, Char, b, c] '[ 'False, 'True, 'False, 'False]
justChar x = Skip `PSkip` x `PCons` Skip `PSkip` Skip `PSkip` PNil
justBool :: Bool -> PList '[a, b, Bool, c] '[ 'False, 'False, 'True, 'False]
justBool x = Skip `PSkip` Skip `PSkip` x `PCons` Skip `PSkip` PNil
justInteger :: Integer -> PList '[a, b, c, Integer] '[ 'False, 'False, 'False, 'True]
justInteger x = Skip `PSkip` Skip `PSkip` Skip `PSkip` x `PCons` PNil
यहां कुछ नमूना उपयोग हैं। useChar
Builder2
उदाहरण का उपयोग कर समाप्त होगा, जबकि noChar
Builder1
उदाहरण का उपयोग करेगा।
useChar :: (Int, Char, Bool, Integer)
useChar = buildRec $ justInt 12 `combine` justBool False `combine` justChar 'c'
noChar :: (Int, Char, Bool, Integer)
noChar = buildRec $ justInt 12 `combine` justBool False
यदि आपको वास्तव में आंशिक चूक की आवश्यकता है तो मुझे नहीं लगता कि एक साफ समाधान है। यदि आप संकलन-समय की सुरक्षा चाहते हैं तो आपको भेदभाव को विभिन्न स्तरों के साथ फू-बिल्डिंग फ़ंक्शंस के माध्यम से, प्रकार के स्तर में भेद लाने की आवश्यकता है, एक आंशिक डिफ़ॉल्ट या एक कन्स्ट्रक्टर प्रति आंशिक डिफ़ॉल्ट के साथ संबंधित 'प्रीफू' योग प्रकार एक ही उद्देश्य की सेवा करने वाले कई अलग-अलग प्री-फू प्रकार। – duplode