2014-05-05 4 views
5

मैं अपनी लाइब्रेरी के उपयोगकर्ताओं को व्यवहार करने के तरीके के नियंत्रण के लिए कार्यों की एक गुच्छा के साथ आपूर्ति करने का एक उचित तरीका समझने की कोशिश कर रहा हूं। मैं उनको कुछ चूक के साथ आपूर्ति करना चाहता हूं जो वे फिट बैठते हैं और ओवरराइड कर सकते हैं। स्पष्ट तरीका (मेरे लिए) Foo {a, b, c, d, e} जैसे कार्यों का रिकॉर्ड है और इसे एक मोनोइड बनाते हैं और फिर कुछ डिफ़ॉल्ट मान प्रदान करते हैं जो वे mappend एक साथ कर सकते हैं। लेकिन मेरे द्वारा किए गए डिफ़ॉल्ट सभी कार्यों को प्रदान नहीं करेंगे। तो मेरे पास {a, b} और {c, d} और {b, c, e} के साथ एक रिकॉर्ड हो सकता है। यह स्पष्ट रूप से सुरक्षित नहीं है, और उपयोगकर्ता मुझे {a, b, c, e} जैसे रिकॉर्ड के साथ आपूर्ति कर सकता है जो खराब होगा। मैं चाहता हूं कि उपयोगकर्ता इस तरह के टुकड़ों को मिलाकर मैच कर सके, लेकिन अभी भी एक पूर्ण रिकॉर्ड के साथ समाप्त होना है।आंशिक रिकॉर्ड के लिए सुरक्षित विकल्प?

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

+2

यदि आपको वास्तव में आंशिक चूक की आवश्यकता है तो मुझे नहीं लगता कि एक साफ समाधान है। यदि आप संकलन-समय की सुरक्षा चाहते हैं तो आपको भेदभाव को विभिन्न स्तरों के साथ फू-बिल्डिंग फ़ंक्शंस के माध्यम से, प्रकार के स्तर में भेद लाने की आवश्यकता है, एक आंशिक डिफ़ॉल्ट या एक कन्स्ट्रक्टर प्रति आंशिक डिफ़ॉल्ट के साथ संबंधित 'प्रीफू' योग प्रकार एक ही उद्देश्य की सेवा करने वाले कई अलग-अलग प्री-फू प्रकार। – duplode

उत्तर

4

आप data-default पैकेज की तलाश में हैं। इसका उपयोग करके, आप अपने प्रकार के लिए डिफ़ॉल्ट मानों को सुरक्षित रूप से प्रारंभ कर सकते हैं। उदाहरण:

import Data.Default 

data Foo = Foo { a :: Int, b :: Int } 

instance Default Foo where 
    def = Foo 3 3 

अब def का उपयोग कर आप किसी भी समारोह में डिफ़ॉल्ट मान का उपयोग कर सकते हैं की जरूरत है:

dummyFun :: Foo -> Foo 
dummyFun x = def 

तुम भी रिकॉर्ड मूल्य बदल सकते हैं जैसा कि आप के लिए आवश्यक:

dummyFun :: Foo -> Foo 
dummyFun x = def { b = 8 } 
+0

कोई भी एकमात्र डिफ़ॉल्ट नहीं है जिसे मैं उनके लिए भर सकता हूं। यही कारण है कि उन्हें सभी कार्यों की आपूर्ति करने की आवश्यकता है। – user3261399

+0

@ user3261399 क्या मनमाने ढंग से चूक प्रदान करने के लिए यह समझदारी होगी? साथ ही, मैं आपका उदाहरण भी सचमुच ले रहा हूं, लेकिन यदि आप अलग-अलग मामलों में, {{ए, बी} ',' {सी, डी} 'और' {ए, बी, सी, ई} 'आंशिक रूप से प्रदान कर सकते हैं डिफ़ॉल्ट तब लगता है कि आपके पास प्रत्येक फ़ील्ड के लिए एक व्यावहारिक डिफ़ॉल्ट है। – duplode

0

आप data प्रकार का उपयोग कर सकते हैं जिसमें बिल्कुल इच्छित संयोजन संयोजन हैं। और अपनी सेटिंग्स monoid की कमजोर बलिदान शर्मिंदा मत बनो। अंत में यह उप-बैंड (जैसे गिट, एचजी, डीएनएफ इत्यादि) के साथ सीएलआई कार्यक्रमों की तरह होगा जिसका विकल्प वाक्यविन्यास अलग है।

0

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 

यहां कुछ नमूना उपयोग हैं। useCharBuilder2 उदाहरण का उपयोग कर समाप्त होगा, जबकि noCharBuilder1 उदाहरण का उपयोग करेगा।

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 
संबंधित मुद्दे