질문은 this 질문과 유사합니다. 그러나 이것은 예외에 관한 것이지 lazy 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
가 쓴 다음, 나는 0으로 나누기를 잡으려고 : 여기
테스트입니다
> 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 $!)
UPDATE 3 여기
다른 '나쁜'예이다.nanothief으로 알 수 있듯이 catch
함수가 항상 예외를 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
따라서 실제 값에 대한'return'이 아니라'catch' 함수에 실제 IO 동작 ('print'과 같은)을 전달해야합니다. – JJJ
@ht : 값을 평가할 때 설명 할 내용을 추가했습니다. –
두 예제 모두 잘 작동합니다. 'fooEvaluated'는 패턴 매칭을위한 나누기를 평가해야하기 때문에 예외가 발생하고 사용자 정의 액션 (이 예제에서는'return 42')으로 처리됩니다. fooNoException'는 나눗셈이 필요 없습니다. 모두 '1'만 반환하면 fooNoException m = const (return 1) (return (3 div m) :: IO Int)'와 동일합니다. 내가 규칙에 대해 물어 보았을 때, 예외를 놓칠 수있는 유일한 방법은 (사용자 정의 액션이 수행되지 않도록)'m div 0','head []','fromJust'에 대한'return'을'catch '하는 것입니다. 아무것도, 어떤 부분적인 기능, 등등. 그것은 정말로 유일한 방법입니까? – JJJ