प्रश्न 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
फ़ंक्शन का उपयोग करें और यह डरावना है।
मैं दो प्रश्न हैं:
- एक सुनिश्चित करें कि
catch
समारोह हमेशा यह की प्रकृति पहला तर्क की परवाह किए बिना सभी अपवादों को पकड़ता है, हो सकता है? - यदि नहीं, तो इस तरह की समस्याओं से निपटने के लिए एक प्रसिद्ध तरीका है?
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
फ़ंक्शन हमेशा किसी भी अपवाद को पकड़ता है। तो इसे ध्यान से उपयोग करने की जरूरत है।
कैसे संबंधित समस्याओं को हल करने पर कुछ सुझाव:
- उपयोग
($!)
return
साथ,catch
का पहला तर्क परforceM
उपयोग करते हैं,catchStrict
समारोह का उपयोग करें। - मैंने यह भी देखा कि कभी-कभी लोग 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
तो नियम यह है कि मुझे वास्तविक आईओ क्रिया (जैसे 'प्रिंट') को 'पकड़' समारोह में पास करने की आवश्यकता है, न कि कुछ शुद्ध मूल्य के लिए 'वापसी' नहीं? – JJJ
@ एचटी .: मूल्यों का मूल्यांकन होने पर समझाए जाने के लिए मैंने अपने उत्तर में और अधिक जोड़ा। –
ठीक है, दोनों उदाहरण अच्छी तरह से काम करते हैं, 'fooEvaluated' को पैटर्न मिलान के लिए विभाजन का मूल्यांकन करने की आवश्यकता है, ताकि अपवाद फेंक दिया जा सके और एक कस्टम कार्रवाई ('मेरे उदाहरण में 42% ') के साथ संभाला जा सके,' fooNoException' को डिवीजन की आवश्यकता नहीं है सब कुछ और केवल '1' देता है, यह' fooNoException m = const (वापसी 1) (वापसी (3 div m) :: IO Int) जैसा ही है। जब मैंने नियम के बारे में पूछा तो मेरा मतलब है कि एक अपवाद को याद करने का एकमात्र तरीका (ताकि एक कस्टम कार्रवाई नहीं की जा सके) 'm div 0',' head [] ',' से 'वापसी' पर 'पकड़ 'है कुछ भी नहीं, कोई आंशिक कार्य, आदि। यह वास्तव में एकमात्र तरीका है? – JJJ