2014-08-27 7 views
13

मैं की तरहQuickCheck: exhaustiveness चेकर का उपयोग कैसे करें राशि प्रकार

data Mytype 
    = C1 
    | C2 Char 
    | C3 Int String 

अगर मैं case एक Mytype पर एक हास्केल डेटा प्रकार है और मामलों में से एक को संभालने के लिए भूल जाते हैं की भूल कंस्ट्रक्टर्स को रोकने के लिए, GHC मुझे देता है एक चेतावनी (निकास जांच)।

मैं अब MyTypes तरह उत्पन्न करने के लिए एक QuickCheck Arbitrary उदाहरण लिखना चाहते हैं:

instance Arbitrary Mytype where 
    arbitrary = do 
    n <- choose (1, 3 :: Int) 
    case n of 
     1 -> C1 
     2 -> C2 <$> arbitrary 
     3 -> C3 <$> arbitrary <*> someCustomGen 

इस के साथ समस्या यह है कि मैं Mytype एक नया विकल्प जोड़ने के लिए और मनमानी उदाहरण अद्यतन करने के लिए, इस प्रकार के होने भूल सकता है मेरी परीक्षण उस विकल्प का परीक्षण नहीं करते हैं।

मैं अपने मनमानी उदाहरण में भूल गए मामलों को याद दिलाने के लिए जीएचसी के थकाऊ चेकर का उपयोग करने का एक तरीका ढूंढना चाहता हूं।

सबसे अच्छा मैं ले कर आए हैं

arbitrary = do 
    x <- elements [C1, C2 undefined, C3 undefined undefined] 
    case x of 
    C1  -> C1 
    C2 _ -> C2 <$> arbitrary 
    C3 _ _ -> C3 <$> arbitrary <*> someCustomGen 

है लेकिन यह वास्तव में सुंदर महसूस नहीं करता है।

मुझे सहजता से लगता है कि इसमें कोई 100% साफ समाधान नहीं है, लेकिन ऐसे मामलों की भूल करने की संभावना कम हो जाएगी - विशेष रूप से एक बड़ी परियोजना में जहां कोड और परीक्षण अलग हो जाते हैं।

+2

बस एक नोट: एक लिख सकते हैं 'सी 2 {}' 'सी 2 के बजाय _' और इतने जिस पर कम से कम एक सा वाक्य रचना अच्छे बनाता है। – nh2

+2

ध्यान दें कि कन्स्ट्रक्टर सख्त होने पर 'अपरिभाषित' चीज़ विफल हो जाएगी। –

+0

क्या कोई कारण है कि आप केवल टी के साथ मनमाना उदाहरण प्राप्त नहीं करना चाहते हैं? –

उत्तर

1

यहां मैं एक अप्रयुक्त चर _x का फायदा उठाता हूं। हालांकि, यह आपके समाधान से वास्तव में अधिक सुरुचिपूर्ण नहीं है।

instance Arbitrary Mytype where 
    arbitrary = do 
    let _x = case _x of C1 -> _x ; C2 _ -> _x ; C3 _ _ -> _x 
    n <- choose (1, 3 :: Int) 
    case n of 
     1 -> C1 
     2 -> C2 <$> arbitrary 
     3 -> C3 <$> arbitrary <*> someCustomGen 
बेशक

, एक _x की डमी परिभाषा के साथ पिछले case सुसंगत रखने के लिए है, इसलिए यह पूरी तरह से सूख नहीं है।

वैकल्पिक रूप से, कोई संकलन-समय दावा करने के लिए टेम्पलेट हास्केल का उपयोग कर सकता है कि Data.Data.dataTypeOf में रचनाकार अपेक्षित हैं। इस जोर को Arbitrary उदाहरण के साथ सुसंगत रखा जाना चाहिए, इसलिए यह पूरी तरह से DRY नहीं है।

यदि आपको कस्टम जनरेटर की आवश्यकता नहीं है, तो मुझे विश्वास है कि Data.Data का उपयोग टेम्पलेट हास्केल के माध्यम से Arbitrary उदाहरण उत्पन्न करने के लिए किया जा सकता है (मुझे लगता है कि मैंने कुछ कोड ठीक से किया है, लेकिन मुझे याद नहीं है)। इस तरह, कोई मौका नहीं है कि उदाहरण एक निर्माता को याद कर सकता है।

+0

मनमाने ढंग से उदाहरण प्राप्त करने के लिए 'जीएचसी.जेनरिक्स' का उपयोग करना एक और संभावना है। 'जीएचसी.जेनरिक्स' उन मामलों में बहुत उपयुक्त है जहां आप कह सकते हैं कि सैम्स (डेटा प्रकार के रचनाकार) और उत्पाद (डेटा कन्स्ट्रक्टर के क्षेत्र) के लिए क्या करना है, जो कि इसका एक उदाहरण होना चाहिए। – bennofs

+0

@bennofs दुर्भाग्य से 'GHC.Generics' केवल तभी मदद करता है जब आप सभी फ़ील्ड में डिफ़ॉल्ट 'मनमानी' का उपयोग करना चाहते हैं। उस स्थिति में, हां, वे इस उद्देश्य के लिए बहुत अच्छे हैं, लेकिन मेरे मामले में यह महत्वपूर्ण है कि मैं इंस्टेंस को कस्टमाइज़ कर सकूं (मैंने 'कुछ कस्टमगेन' सहित) पर संकेत देने की कोशिश की। – nh2

+0

आपके द्वारा प्रस्तावित आत्म-पुनरावर्ती तरीका निश्चित रूप से मेरे 'अपरिभाषित' से बेहतर है। – nh2

1

मैंने टेम्पलेट हास्केल के साथ एक समाधान लागू किया, आप https://gist.github.com/nh2/d982e2ca4280a03364a8 पर प्रोटोटाइप पा सकते हैं। इस के साथ आप लिख सकते हैं:

instance Arbitrary Mytype where 
    arbitrary = oneof $(exhaustivenessCheck ''Mytype [| 
     [ pure C1 
     , C2 <$> arbitrary 
     , C3 <$> arbitrary <*> arbitrary 
     ] 
    |]) 

यह इस तरह काम करता है: आप इसे एक प्रकार का नाम (जैसे ''Mytype) और एक अभिव्यक्ति (arbitrary शैली Gen रों की एक सूची मेरे मामले में) दे। यह उस प्रकार के नाम के लिए सभी रचनाकारों की सूची प्राप्त करता है और जांच करता है कि अभिव्यक्ति में इन सभी रचनाकारों को कम से कम एक बार शामिल किया गया है या नहीं। यदि आपने अभी एक कन्स्ट्रक्टर जोड़ा है लेकिन इसे मनमाने ढंग से उदाहरण में जोड़ना भूल गया है, तो यह फ़ंक्शन आपको संकलित समय पर चेतावनी देगा।

यह है कि यह कैसे वें के साथ लागू किया गया है:

exhaustivenessCheck :: Name -> Q Exp -> Q Exp 
exhaustivenessCheck tyName qList = do 
    tyInfo <- reify tyName 
    let conNames = case tyInfo of 
     TyConI (DataD _cxt _name _tyVarBndrs cons _derives) -> map conNameOf cons 
     _ -> fail "exhaustivenessCheck: Can only handle simple data declarations" 

    list <- qList 
    case list of 
    [email protected](ListE l) -> do 
     -- We could be more specific by searching for `ConE`s in `l` 
     let cons = toListOf tinplate l :: [Name] 
     case filter (`notElem` cons) conNames of 
     [] -> return input 
     missings -> fail $ "exhaustivenessCheck: missing case: " ++ show missings 
    _ -> fail "exhaustivenessCheck: argument must be a list" 

मैं GHC.Generics उपयोग कर रहा हूँ आसानी से Exp की वाक्य रचना पेड़ पार करने के लिए: के साथ toListOf tinplate exp :: [Name] (lens से) मैं आसानी से पूरे में सभी Name रों पा सकते हैं exp

मुझे आश्चर्य है कि Language.Haskell.TH से प्रकार Generic उदाहरणों की जरूरत नहीं है था, और न (वर्तमान GHC 7.8 के साथ) Integer या Word8 है - इन के लिए Generic उदाहरणों के लिए आवश्यक हैं, क्योंकि वे Exp में दिखाई देते हैं। इसलिए मैंने उन्हें अनाथ उदाहरणों के रूप में जोड़ा (अधिकांश चीजों के लिए, StandaloneDeriving ऐसा करता है लेकिन Integer जैसे आदिम प्रकारों के लिए मुझे Int के रूप में उदाहरणों की प्रतिलिपि बनाना था)।

समाधान सही नहीं है क्योंकि यह case जैसे थकाऊ चेकर का उपयोग नहीं करता है, लेकिन जैसा कि हम सहमत हैं, DRY रहने के दौरान यह संभव नहीं है, और यह TH समाधान DRY है।

एक संभावित सुधार/विकल्प एक TH फ़ंक्शन लिखना होगा जो प्रत्येक मनमाना उदाहरण के अंदर exhaustivenessCheck पर कॉल करने के बजाय एक बार पूरे मॉड्यूल में सभी मनमाना उदाहरणों के लिए जांच करता है।

1

आप यह सुनिश्चित करना चाहते हैं कि आपका कोड किसी विशेष तरीके से व्यवहार करे; कोड के व्यवहार की जांच करने का सबसे आसान तरीका यह परीक्षण करना है।

इस मामले में, वांछित व्यवहार यह है कि प्रत्येक निर्माता को परीक्षणों में उचित कवरेज मिलता है। हम इसे एक साधारण परीक्षण के साथ देख सकते हैं:

allCons xs = length xs > 100 ==> length constructors == 3 
      where constructors = nubBy eqCons xs 
        eqCons C1  C1  = True 
        eqCons C1  _  = False 
        eqCons (C2 _) (C2 _) = True 
        eqCons (C2 _) _  = False 
        eqCons (C3 _ _) (C3 _ _) = True 
        eqCons (C3 _ _) _  = False 

यह बहुत अच्छा है, लेकिन यह एक अच्छा पहला शॉट है। इसके फायदे:

  • eqCons चेतावनी नई कंस्ट्रक्टर्स जोड़े जाते हैं, जो कि आप क्या चाहते हैं
  • यह जाँच करता है कि आपके उदाहरण सभी निर्माताओं से निपटने है, जो है तुम क्या चाहते
  • यह भी एक exhaustiveness ट्रिगर किया जाएगा जांच करता है कि सभी निर्माताओं वास्तव में कुछ उपयोगी संभावना के साथ उत्पन्न कर रहे हैं
  • यह भी जांच करता है कि आपके उदाहरण है प्रयोग करने योग्य है, जैसे (इस मामले में कम से कम 1% में) ।

इसका नुकसान लटका नहीं करता है:

  • परीक्षण डेटा की एक बड़ी राशि की आवश्यकता है, ताकि लंबाई के साथ उन लोगों को फिल्टर करने में> 100
  • eqCons काफी वर्बोज़ है, क्योंकि एक कैच-ऑल eqCons _ _ = False exhaustiveness जाँच
  • बाईपास होगा जादू संख्या का उपयोग करता है 100 और 3
  • बहुत सामान्य नहीं

इस में सुधार करने के तरीके हैं, उदाहरण के लिए। हम डेटा का उपयोग कर रचनाकारों की गणना कर सकते हैं।डाटा मॉड्यूल:

allCons xs = sufficient ==> length constructors == consCount 
      where sufficient = length xs > 100 * consCount 
        constructors = length . nub . map toConstr $ xs 
        consCount = dataTypeConstrs (head xs) 

यह संकलन समय exhaustiveness जांच खो देता है, लेकिन यह निरर्थक रूप में लंबे समय है हम नियमित रूप से परीक्षण करने के लिए और हमारे कोड अधिक सामान्य बन गया है।

हम वास्तव में exhaustiveness जांच चाहते हैं, तो कुछ स्थानों पर जहां हम इसे वापस जूता-सींग सकता है में हैं:

allCons xs = sufficient ==> length constructors == consCount 
      where sufficient = length xs > 100 * consCount 
        constructors = length . nub . map toConstr $ xs 
        consCount = length . dataTypeConstrs $ case head xs of 
                    [email protected](C1)  -> x 
                    [email protected](C2 _) -> x 
                    [email protected](C3 _ _) -> x 

सूचना है कि हम consCount का उपयोग जादू 3 पूरी तरह से खत्म करने के लिए। जादू 100 (जो कि कन्स्ट्रक्टर की न्यूनतम आवश्यक आवृत्ति निर्धारित करता है) अब consCount के साथ स्केल करता है, लेकिन इसके लिए केवल और भी परीक्षण डेटा की आवश्यकता होती है!

हम जानते हैं कि काफी आसानी से हल एक newtype का उपयोग कर सकते हैं:

consCount = length (dataTypeConstrs C1) 

newtype MyTypeList = MTL [MyType] deriving (Eq,Show) 

instance Arbitrary MyTypeList where 
    arbitrary = MTL <$> vectorOf (100 * consCount) arbitrary 
    shrink (MTL xs) = MTL (shrink <$> xs) 

allCons (MTL xs) = length constructors == consCount 
        where constructors = length . nub . map toConstr $ xs 

हम कहीं में एक साधारण exhaustiveness जांच रख सकते हैं अगर हम चाहते है, जैसे।

instance Arbitrary MyTypeList where 
    arbitrary = do x <- arbitrary 
       MTL <$> vectorOf (100 * consCount) getT 
       where getT = do x <- arbitrary 
           return $ case x of 
              C1  -> x 
              C2 _ -> x 
              C3 _ _ -> x 
    shrink (MTL xs) = MTL (shrink <$> xs) 
संबंधित मुद्दे