2012-06-22 12 views
9

प्रश्न this प्रश्न के समान है। हालांकि, यह अपमान के बारे में है, आलसी I/O के बारे में नहीं।हास्केल में आलस्य और अपवाद कैसे काम करते हैं?

{-# LANGUAGE ScopedTypeVariables #-} 

import Prelude hiding (catch) 
import Control.Exception 

fooLazy :: Int -> IO Int 
fooLazy m = return $ 1 `div` m 

fooStrict :: Int -> IO Int 
fooStrict m = return $! 1 `div` m 

test :: (Int -> IO Int) -> IO() 
test f = print =<< f 0 `catch` \(_ :: SomeException) -> return 42 

testLazy :: Int -> IO Int 
testLazy m = (return $ 1 `div` m) `catch` \(_ :: SomeException) -> return 42 

testStrict :: Int -> IO Int 
testStrict m = (return $! 1 `div` m) `catch` \(_ :: SomeException) -> return 42 

तो मैंने लिखा दो कार्य fooLazy जो आलसी है और fooStrict जो सख्त है, यह भी वहाँ दो परीक्षणों testLazy और testStrict है, तो मैं शून्य से भाग को पकड़ने की कोशिश:

यहाँ एक परीक्षण है

> test fooLazy 
*** Exception: divide by zero 
> test fooStrict 
42 
> testLazy 0 
*** Exception: divide by zero 
> testStrict 0 
42 

और यह आलसी मामलों में विफल रहता है।

पहली बात यह है कि मन में आता है कि इसके पहले तर्क पर मूल्यांकन के लिए मजबूर catch समारोह का एक संस्करण लिखने के लिए:

> test fooLazy 
42 
> test fooStrict 
42 
> testLazy 0 
42 
> testStrict 0 
42 

लेकिन मैं:

{-# LANGUAGE ScopedTypeVariables #-} 

import Prelude hiding (catch) 
import Control.DeepSeq 
import Control.Exception 
import System.IO.Unsafe 

fooLazy :: Int -> IO Int 
fooLazy m = return $ 1 `div` m 

fooStrict :: Int -> IO Int 
fooStrict m = return $! 1 `div` m 

instance NFData a => NFData (IO a) where 
    rnf = rnf . unsafePerformIO 

catchStrict :: (Exception e, NFData a) => IO a -> (e -> IO a) -> IO a 
catchStrict = catch . force 

test :: (Int -> IO Int) -> IO() 
test f = print =<< f 0 `catchStrict` \(_ :: SomeException) -> return 42 

testLazy :: Int -> IO Int 
testLazy m = (return $ 1 `div` m) `catchStrict` \(_ :: SomeException) -> return 42 

testStrict :: Int -> IO Int 
testStrict m = (return $! 1 `div` m) `catchStrict` \(_ :: SomeException) -> return 42 

यह काम करने लगता है यहां unsafePerformIO फ़ंक्शन का उपयोग करें और यह डरावना है।

मैं दो प्रश्न हैं:

  1. एक सुनिश्चित करें कि catch समारोह हमेशा यह की प्रकृति पहला तर्क की परवाह किए बिना सभी अपवादों को पकड़ता है, हो सकता है?
  2. यदि नहीं, तो इस तरह की समस्याओं से निपटने के लिए एक प्रसिद्ध तरीका है? catchStrict फ़ंक्शन की तरह कुछ उपयुक्त है?

UPDATE 1

यह nanothief द्वारा catchStrict समारोह का एक बेहतर संस्करण है:

forceM :: (Monad m, NFData a) => m a -> m a 
forceM m = m >>= (return $!) . force 

catchStrict :: (Exception e, NFData a) => IO a -> (e -> IO a) -> IO a 
catchStrict expr = (forceM expr `catch`) 

अद्यतन 2

main :: IO() 
main = do 
    args <- getArgs 
    res <- return ((+ 1) $ read $ head args) `catch` \(_ :: SomeException) -> return 0 
    print res 

यह इस तरह फिर से लिखा जाना चाहिए::

main :: IO() 
main = do 
    args <- getArgs 
    print ((+ 1) $ read $ head args) `catch` \(_ :: SomeException) -> print 0 
-- or 
-- 
-- res <- return ((+ 1) $ read $ head args) `catchStrict` \(_ :: SomeException) -> return 0 
-- print res 
-- 
-- or 
-- 
-- res <- returnStrcit ((+ 1) $ read $ head args) `catch` \(_ :: SomeException) -> return 0 
-- print res 
-- 
-- where 
returnStrict :: Monad m => a -> m a 
returnStrict = (return $!) 

अद्यतन 3

यहाँ एक और 'बुरे' उदाहरण है।

nanothief के रूप में, इस बात की कोई गारंटी नहीं है कि catch फ़ंक्शन हमेशा किसी भी अपवाद को पकड़ता है। तो इसे ध्यान से उपयोग करने की जरूरत है।

कैसे संबंधित समस्याओं को हल करने पर कुछ सुझाव:

  1. उपयोग ($!)return साथ, catch का पहला तर्क पर forceM उपयोग करते हैं, catchStrict समारोह का उपयोग करें।
  2. मैंने यह भी देखा कि कभी-कभी लोग add some strictness उनके ट्रांसफॉर्मर के उदाहरणों के लिए।

    {-# LANGUAGE GeneralizedNewtypeDeriving, TypeSynonymInstances, FlexibleInstances 
        , MultiParamTypeClasses, UndecidableInstances, ScopedTypeVariables #-} 
    
    import System.Environment 
    
    import Prelude hiding (IO) 
    import qualified Prelude as P (IO) 
    import qualified Control.Exception as E 
    import Data.Foldable 
    import Data.Traversable 
    import Control.Applicative 
    import Control.Monad.Trans 
    import Control.Monad.Error 
    
    newtype StrictT m a = StrictT { runStrictT :: m a } deriving 
        (Foldable, Traversable, Functor, Applicative, Alternative, MonadPlus, MonadFix 
        , MonadIO 
    ) 
    
    instance Monad m => Monad (StrictT m) where 
        return = StrictT . (return $!) 
        m >>= k = StrictT $ runStrictT m >>= runStrictT . k 
        fail = StrictT . fail 
    
    instance MonadTrans StrictT where 
        lift = StrictT 
    
    type IO = StrictT P.IO 
    
    instance E.Exception e => MonadError e IO where 
        throwError = StrictT . E.throwIO 
        catchError m h = StrictT $ runStrictT m `E.catch` (runStrictT . h) 
    
    io :: StrictT P.IO a -> P.IO a 
    io = runStrictT 
    

    यह the identity monad transformer, लेकिन साथ सख्त return अनिवार्य है:

यहाँ एक उदाहरण है

foo :: Int -> IO Int 
foo m = return $ 1 `div` m 

fooReadLn :: Int -> IO Int 
fooReadLn x = liftM (`div` x) $ liftIO readLn 

test :: (Int -> IO Int) -> P.IO() 
test f = io $ liftIO . print =<< f 0 `catchError` \(_ :: E.SomeException) -> return 42 

main :: P.IO() 
main = io $ do 
    args <- liftIO getArgs 
    res <- return ((+ 1) $ read $ head args) `catchError` \(_ :: E.SomeException) -> return 0 
    liftIO $ print res 

-- > test foo 
-- 42 
-- > test fooReadLn 
-- 1 
-- 42 
-- ./main 
-- 0 

उत्तर

8

सबसे पहले (मुझे यकीन है कि आप इस पहले से ही पता है, तो नहीं कर रहा हूँ), कारण आलसी आलसी मामले के साथ काम नहीं करता है

1 `div` 0 

अभिव्यक्ति का मूल्यांकन तब तक नहीं किया जाता जब तक इसकी आवश्यकता न हो, जो print फ़ंक्शन के अंदर है। हालांकि, catch विधि केवल f 0 अभिव्यक्ति पर लागू होती है, न कि संपूर्ण print =<< f 0 अभिव्यक्ति, इसलिए अपवाद पकड़ा नहीं जाता है। यदि आपने किया:

test f = (print =<< f 0) `catch` \(_ :: SomeException) -> print 42 

इसके बजाय, यह दोनों मामलों में सही ढंग से काम करता है।

आपको एक बयान बनाना चाहते हैं कि हालांकि आईओ परिणाम की पूरी मूल्यांकन मजबूर करता है, बजाय NFData का एक नया उदाहरण बनाने की, तो आप एक forceM विधि लिख सकता है, और का उपयोग करें कि catchStrict विधि में:

forceM :: (Monad m, NFData a) => m a -> m a 
forceM m = m >>= (return $!) . force 

catchStrict :: (Exception e, NFData a) => IO a -> (e -> IO a) -> IO a 
catchStrict expr = (forceM expr `catch`) 

:

अपनी टिप्पणी के बारे में


(मैं थोड़ा आश्चर्य है कि forceM Control.DeepSeq पुस्तकालय के अंदर नहीं है हूँ)

नहीं, नियम अपवाद केवल तभी फेंक दिया जाता है जब मान गणना की जाती है, और यह केवल तब किया जाता है जब इसे हैकेल द्वारा आवश्यक हो। और यदि हैकेल कुछ के मूल्यांकन में देरी कर सकता है तो यह होगा।

एक उदाहरण परीक्षण समारोह है कि $! उपयोग नहीं करता है, लेकिन अभी भी एक अपवाद सीधे कारण बनता है (ताकि सामान्य पकड़ शून्य अपवाद द्वारा विभाजित पकड़ेगा) है:

fooEvaluated :: Int -> IO Int 
fooEvaluated m = case 3 `div` m of 
    3 -> return 3 
    0 -> return 0 
    _ -> return 1 

हास्केल के लिए मजबूर किया जाता है का मूल्यांकन "3` div` m "अभिव्यक्ति, क्योंकि इसे 3 और 0 के खिलाफ परिणाम मिलान करने की आवश्यकता है।

अंतिम उदाहरण के रूप में, निम्नलिखित कोई अपवाद नहीं फेंकता है, और जब परीक्षण फ़ंक्शन के साथ उपयोग किया जाता है तो 1:

fooNoException :: Int -> IO Int 
fooNoException m = case 3 `div` m of 
    _ -> return 1 

ऐसा इसलिए है क्योंकि हैकेल को कभी भी "3` div` m "अभिव्यक्ति की गणना करने की आवश्यकता नहीं है (_ सब कुछ मेल खाता है), इसलिए इसकी गणना कभी नहीं की जाती है, इसलिए कोई अपवाद नहीं फेंक दिया जाता है।

+0

तो नियम यह है कि मुझे वास्तविक आईओ क्रिया (जैसे 'प्रिंट') को 'पकड़' समारोह में पास करने की आवश्यकता है, न कि कुछ शुद्ध मूल्य के लिए 'वापसी' नहीं? – JJJ

+0

@ एचटी .: मूल्यों का मूल्यांकन होने पर समझाए जाने के लिए मैंने अपने उत्तर में और अधिक जोड़ा। –

+0

ठीक है, दोनों उदाहरण अच्छी तरह से काम करते हैं, 'fooEvaluated' को पैटर्न मिलान के लिए विभाजन का मूल्यांकन करने की आवश्यकता है, ताकि अपवाद फेंक दिया जा सके और एक कस्टम कार्रवाई ('मेरे उदाहरण में 42% ') के साथ संभाला जा सके,' fooNoException' को डिवीजन की आवश्यकता नहीं है सब कुछ और केवल '1' देता है, यह' fooNoException m = const (वापसी 1) (वापसी (3 div m) :: IO Int) जैसा ही है। जब मैंने नियम के बारे में पूछा तो मेरा मतलब है कि एक अपवाद को याद करने का एकमात्र तरीका (ताकि एक कस्टम कार्रवाई नहीं की जा सके) 'm div 0',' head [] ',' से 'वापसी' पर 'पकड़ 'है कुछ भी नहीं, कोई आंशिक कार्य, आदि। यह वास्तव में एकमात्र तरीका है? – JJJ

संबंधित मुद्दे