2010-01-26 9 views
6

के साथ कई वस्तुओं की त्रुटि जांच करने का तरीका हैस्केल फ़ंक्शन के लिए कई अलग-अलग स्थितियों की जांच करने और विफलता पर त्रुटि संदेश लौटने का एक अच्छा तरीका क्या है?हास्केल

अजगर या इसी तरह की भाषा में, यह स्पष्ट होगा:

if failure_1: 
    return "test1 failed" 
if failure_2: 
    return "test2 failed" 
... 
if failure_n: 
    return "testn failed" 
do_computation 

आप बयान करता है, तो हास्केल में मनमाने ढंग से नेस्ट मामले के बिना यह कर कैसे करते हैं /?

संपादित करें: कुछ परीक्षण स्थितियों में आईओ की आवश्यकता हो सकती है जो आईओ monad में कोई परीक्षण परिणाम डालता है। मेरा मानना ​​है कि यह कई समाधानों में एक कंक डालता है।

उत्तर

12

तो, आप ई IO के अंदर फंस गया, और आप बिना घोंसले if एस के बिना परिस्थितियों का एक गुच्छा देखना चाहते हैं। मुझे उम्मीद है कि आप मुझे जवाब देने के तरीके से हास्केल में हल करने वाली अधिक सामान्य समस्या पर एक अवसाद को माफ कर देंगे।

इस बात पर विचार करें कि इसे कैसे व्यवहार करने की आवश्यकता है।

  • सफलता, जिस स्थिति में कार्यक्रम समारोह
  • विफलता, जिस स्थिति में कार्यक्रम समारोह के बाकी को छोड़ देता है और त्रुटि संदेश देता है के बाकी चलाता है: एक शर्त जाँच हो रही है दो परिणामों में से एक है।

कई स्थितियों की जांच बार-बार देखी जा सकती है; प्रत्येक बार जब यह "शेष कार्य" चलाता है, तो यह अंतिम स्थिति तक पहुंचने तक, अगली स्थिति तक पहुंच जाता है, जो परिणाम देता है। अब, समस्या को हल करने के पहले चरण के रूप में, चलो उस संरचना का उपयोग करके चीजों को अलग कर दें - इसलिए मूल रूप से, हम अनियंत्रित स्थितियों का एक गुच्छा टुकड़ों में बदलना चाहते हैं जिन्हें हम एक बहु-सशर्त कार्य में एक साथ लिख सकते हैं। इन टुकड़ों की प्रकृति के बारे में हम क्या निष्कर्ष निकाल सकते हैं?

1) प्रत्येक टुकड़ा दो अलग-अलग प्रकारों में से एक को वापस कर सकता है; एक त्रुटि संदेश, या अगले चरण का परिणाम।

2) प्रत्येक टुकड़े को यह तय करना होगा कि अगले चरण को चलाने के लिए, तो चरणों को संयोजित करते समय हमें इसे अगले चरण को तर्क के रूप में प्रस्तुत करने की आवश्यकता होती है।

3) चूंकि प्रत्येक टुकड़ा को समान संरचना को संरक्षित करने के लिए अगला कदम दिया जाने की उम्मीद है, इसलिए हमें अंतिम, बिना शर्त कदम को एक सशर्त चरण के समान दिखने के लिए एक तरीका चाहिए।

पहली आवश्यकता स्पष्ट रूप से सुझाव देती है कि हम अपने परिणामों के लिए Either String a जैसे प्रकार चाहते हैं। अब हमें दूसरी आवश्यकता फिट करने के लिए एक संयोजन समारोह की आवश्यकता है, और तीसरे फिट करने के लिए एक रैपिंग फ़ंक्शन की आवश्यकता है। इसके अतिरिक्त, चरणों को संयोजित करते समय, हम पिछले चरण से डेटा तक पहुंच सकते हैं (कहें, दो अलग-अलग इनपुट मान्य करना, फिर जांच कर रहे हैं कि वे बराबर हैं), इसलिए प्रत्येक चरण को पिछले चरण के परिणाम को तर्क के रूप में लेने की आवश्यकता होगी।

तो, प्रत्येक चरण err a के प्रकार को एक शॉर्टेंड के रूप में कॉल करना, अन्य कार्यों के किस प्रकार के हो सकते हैं?

combineSteps :: err a -> (a -> err b) -> err b 
wrapFinalStep :: a -> err a 

ठीक है, उन प्रकार के हस्ताक्षर अजीब परिचित लगते हैं, है ना?

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

तो, हम सिर्फ मॉड्यूल आयात कर सकते हैं, एक गर्म फजी ErrorT में IO लपेट के लिए एक प्रकार का पर्याय है, और दूर तुम जाओ:

import Control.Monad.Error 

type EIO a = ErrorT String IO a 

assert pred err = if pred then return() else throwError err 

askUser prompt = do 
    liftIO $ putStr prompt 
    liftIO getLine 

main :: IO (Either String()) 
main = runErrorT test 

test :: EIO() 
test = do 
    x1 <- askUser "Please enter anything but the number 5: " 
    assert (x1 /= "5") "Entered 5" 
    x2 <- askUser "Please enter a capital letter Z: " 
    assert (x2 == "Z") "Didn't enter Z" 
    x3 <- askUser "Please enter the same thing you entered for the first question: " 
    assert (x3 == x1) $ "Didn't enter " ++ x1 
    return() -- superfluous, here to make the final result more explicit 

test चल रहा है, जैसा कि आप उम्मीद करेंगे के परिणाम, सफलता के लिए Right() या विफलता के लिए Left String है, जहां String उचित संदेश है; और यदि assert विफलता लौटाता है, तो निम्न में से कोई भी कार्य नहीं किया जाएगा।

IO कार्यों के परिणाम के परीक्षण के लिए आप assert के लिए इसी तरह एक सहायक समारोह है कि बजाय IO Bool का एक तर्क, या कुछ अन्य दृष्टिकोण लेता है लिखने के लिए यह सबसे आसान मिल सकता है।

इसके अलावा liftIO के उपयोग EIO में मूल्यों में IO कार्यों कन्वर्ट करने के लिए, और runErrorT एक EIO कार्रवाई चलाने के लिए और कुल परिणाम के साथ Either String a मान देने के लिए ध्यान दें। यदि आप अधिक जानकारी चाहते हैं तो आप monad transformers पर पढ़ सकते हैं।

+0

बहुत अच्छा जवाब! –

+0

मुझे त्रुटि monad प्यार करता हूँ! कंपाइलर्स में नकली त्रुटि संदेशों को घुमाने के लिए भी बहुत अच्छा .... +1 –

+1

@ नॉर्मन रैमसे: काफी! हास्केल के साथ अपने शुरुआती रोमांच में, त्रुटियों के लिए 'या तो' का उपयोग करके पुस्तकालयों को पूरा करने पर, मैं जल्दी से 'राइट' से निकालने के लिए मूल्यों को पार करने की कठोरता से नाराज हो गया; इसलिए मैंने उन कार्यों को लिखा जो (हिंडसाइट में) लगभग 'fmap' और '(>> =)' के बराबर थे। लगभग उसी समय जब मैंने मोनैडिक संरचना को पहचानने के लिए समझ हासिल की, मैंने पाया कि 'मोनाड इरर' सभी के साथ अस्तित्व में था, और मेरे कच्चे पुनर्विचारों के लिए कुछ मूर्खतापूर्ण महसूस किया।"फैंसी" monads पर सभी झगड़े में, ऐसा लगता है कि monadic 'या तो 'और' शायद' भूल गया है ... –

5

आम तौर पर पैटर्न मिलान अगर बयान, और जाँच त्रुटि की स्थिति कोई अपवाद नहीं है की बहुत सारी की तुलना में जाने के लिए एक बेहतर तरीका है:

func :: [Int] -> Either String Int 
func [] = Left "Empty lists are bad" 
func [x] 
    | x < 0 = Left "Negative? Really?" 
    | odd x = Left "Try an even number" 
func xs = Right (length xs) 

यह फ़ंक्शन या तो एक त्रुटि संदेश या पैरामीटर की लंबाई। त्रुटि मामलों को पहले और केवल तभी प्रयास किया जाता है जब उनमें से कोई भी अंतिम मामले से मेल नहीं खाता है।

+0

धन्यवाद यह एक अच्छा विचार है। मैंने मूल रूप से उल्लेख नहीं किया है कि मेरी कुछ परीक्षण स्थितियों में आईओ की आवश्यकता है। पैटर्न पैटर्न में आईओ कंडशन की जांच करने का कोई तरीका है? – me2

-1

उपयोग गार्ड:

f z 
    | failure_1 = ... 
    | failure_2 = ... 
    | failure_3 = ... 
    | failure_4 = ... 
    | otherwise = do_computation 
1

मुझे नहीं लगता कि आप एक गार्ड में आईओ का उपयोग कर सकते है।

myIoAction filename = foldr ($) [noFile, fileTooLarge, notOnlyFile] do_computation 
    where do_computation 
      = do {- do something -} 
       return (Right answer) 
     noFile success 
      = do {- find out whether file exists -} 
       if {- file exists -} then success else return (Left "no file!") 
     fileTooLarge success 
      = do {- find out size of file -} 
       if maxFileSize < fileSize then return (Left "file too large") else success 
     -- etc 
1

अपने other question इस पर एक इच्छित संशोधन, आप एक स्विच/मामले बयान की तरह कुछ बना सकते हैं के रूप में

select :: Monad m => [(m Bool, m a)] -> m a -> m a 
select fallback [] = fallback 
select fallback ((check, action) : others) = do 
    ok <- check 
    if ok then action else select fallback others 

newfile :: FilePath -> IO Bool 
newfile x = select 
    (return True) 
    [ (return $ length x <= 0, return False) 
    , (doesFileExist x,  return False) ] 

इस हालांकि ले रहा है:

इसके बजाय, आप कुछ इस तरह कर सकता है विशेष रूप से आसानी से लिखा जा सकता है

newFile [] = return False 
newFile fn = fmap not $ doesFileExist fn