2012-06-22 2 views
9

질문은 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은 또한이 개 시험 testLazytestStrict가 쓴 다음, 나는 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 기능을 사용하십시오. 이것은 무서운 것입니다.

나는이 두 가지 질문 :

  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 $!) 

UPDATE 3 여기

다른 '나쁜'예이다.

nanothief으로 알 수 있듯이 catch 함수가 항상 예외를 catch한다는 보장은 없습니다. 따라서 신중하게 사용해야합니다.관련 문제를 해결하는 방법에 대한

몇 가지 팁 : return

  1. 사용 ($!)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의 새로운 인스턴스를 만드는의 IO 결과의 완전한 평가를 강제로 비록 당신이 catch 문을 확인하려면

, 당신은 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 라이브러리 내부 아니라고 놀라게 해요)

아니요, 규칙은 값이 계산 될 때 예외가 throw되며 haskell에서 필요로 할 때만 완료됩니다. 그리고 만약 haskell이 무언가의 평가를 지연시킬 수 있다면.

$!를 사용하지만, 여전히 바로 예외가 발생 (그래서 정상 캐치 제로 예외로 격차를 잡을 것)하지 않는 예 테스트 기능은 다음과 같습니다

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

하스켈가 강제로 평가 된 결과가 3과 0에 일치해야하므로 "3 div. m"표현식을 사용해야합니다.

마지막 예에서 다음은 예외를 발생시키지 않으며 테스트 함수와 함께 사용하면 1을 반환합니다 :

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

haskell은 "3 divm"표현식을 계산할 필요가 없기 때문에 (따라서 _은 모두 일치 함) 계산되지 않으므로 예외가 발생하지 않습니다.

+0

따라서 실제 값에 대한'return'이 아니라'catch' 함수에 실제 IO 동작 ('print'과 같은)을 전달해야합니다. – JJJ

+0

@ht : 값을 평가할 때 설명 할 내용을 추가했습니다. –

+0

두 예제 모두 잘 작동합니다. 'fooEvaluated'는 패턴 매칭을위한 나누기를 평가해야하기 때문에 예외가 발생하고 사용자 정의 액션 (이 예제에서는'return 42')으로 처리됩니다. fooNoException'는 나눗셈이 필요 없습니다. 모두 '1'만 반환하면 fooNoException m = const (return 1) (return (3 div m) :: IO Int)'와 동일합니다. 내가 규칙에 대해 물어 보았을 때, 예외를 놓칠 수있는 유일한 방법은 (사용자 정의 액션이 수행되지 않도록)'m div 0','head []','fromJust'에 대한'return'을'catch '하는 것입니다. 아무것도, 어떤 부분적인 기능, 등등. 그것은 정말로 유일한 방법입니까? – JJJ

관련 문제