Haskell의 예외 처리


79

세 가지 Haskell 함수의 사용법을 이해하려면 도움이 필요합니다.

  • 시도 ( Control.Exception.try :: Exception e => IO a -> IO (Either e a))
  • 잡기 ( Control.Exception.catch :: Exception e => IO a -> (e -> IO a) -> IO a)
  • 핸들 ( Control.Exception.handle :: Exception e => (e -> IO a) -> IO a -> IO a)

몇 가지를 알아야합니다.

  1. 언제 어떤 기능을 사용합니까?
  2. 간단한 예제로이 기능을 어떻게 사용합니까?
  3. 캐치와 핸들의 차이점은 무엇입니까? 서로 다른 순서로만 거의 동일한 서명이 있습니다.

나는 내 시련을 적어보고 당신이 나를 도울 수 있기를 바랍니다.

시험

다음과 같은 예가 있습니다.

x = 5 `div` 0
test = try (print x) :: IO (Either SomeException ())

두 가지 질문이 있습니다.

  1. 사용자 지정 오류 출력을 어떻게 설정할 수 있습니까?

  2. 모든 오류를 SomeException으로 설정하려면 어떻게해야하나요? :: IO (Either SomeException())

캐치 / 시도

사용자 지정 오류 출력이있는 간단한 예를 보여줄 수 있습니까?


답변:


132

언제 어떤 기능을 사용합니까?

Control.Exception 설명서의 권장 사항은 다음과 같습니다.

  • 예외가 발생한 경우 정리를 수행하려면 finally, bracket또는을 사용하십시오 onException.
  • 예외 후에 복구하고 다른 작업을 수행하려면 try가족 중 하나를 사용하는 것이 가장 좋습니다 .
  • ... 비동기 예외에서 복구하지 않는 한,이 경우 catch또는 catchJust.

try :: 예외 e => IO a-> IO (둘 중 하나)

try소요 IO실행에 조치를, 그리고를 반환합니다 Either. 계산이 성공하면 결과가 Right생성자에 래핑됩니다 . (잘못된 것이 아니라 옳다고 생각하십시오). 작업 에서 지정된 유형 의 예외 발생 하면 Left생성자 에서 반환됩니다 . 예외가 적절한 유형 이 아닌 경우 스택 위로 계속 전파됩니다. SomeException유형으로 지정 하면 모든 예외가 포착되며 이는 좋은 생각 일 수도 있고 아닐 수도 있습니다.

순수 계산에서 예외를 포착하려면을 사용 evaluate하여 try.

main = do
    result <- try (evaluate (5 `div` 0)) :: IO (Either SomeException Int)
    case result of
        Left ex  -> putStrLn $ "Caught exception: " ++ show ex
        Right val -> putStrLn $ "The answer was: " ++ show val

catch :: 예외 e => IO a-> (e-> IO a)-> IO a

catch와 유사합니다 try. 먼저 지정된 IO작업 을 실행하려고 시도 하지만 예외가 발생하면 처리기에 예외가 주어져 대체 응답을 얻습니다.

main = catch (print $ 5 `div` 0) handler
  where
    handler :: SomeException -> IO ()
    handler ex = putStrLn $ "Caught exception: " ++ show ex

그러나 한 가지 중요한 차이점이 있습니다. catch핸들러를 사용할 때 비동기 예외 (예 :를 통해 다른 스레드에서 발생)로 인해 중단 될 수 없습니다 throwTo. 비동기 예외를 발생시키려는 시도는 핸들러 실행이 완료 될 때까지 차단됩니다.

catchPrelude 에는 다른 기능 이 있으므로 import Prelude hiding (catch).

핸들 :: 예외 e => (e-> IO a)-> IO a-> IO a

handle단순히 catch반대 순서의 인수를 사용합니다. 사용할 코드는 코드를 더 읽기 쉽게 만드는 요소 또는 부분 응용 프로그램을 사용하려는 경우 어떤 것이 더 적합한 지에 따라 다릅니다. 그렇지 않으면 동일합니다.

tryJust, catchJust and handleJust

참고로 try, catch그리고 handle잡을 것 모든 지정된 / 추론 유형의 예외. tryJust친구를 사용하면 특별히 처리하려는 예외를 필터링하는 선택기 기능을 지정할 수 있습니다. 예를 들어, 모든 산술 오류는 유형 ArithException입니다. 만 잡으려는 DivideByZero경우 다음을 수행 할 수 있습니다.

main = do
    result <- tryJust selectDivByZero (evaluate $ 5 `div` 0)
    case result of
        Left what -> putStrLn $ "Division by " ++ what
        Right val -> putStrLn $ "The answer was: " ++ show val
  where
    selectDivByZero :: ArithException -> Maybe String
    selectDivByZero DivideByZero = Just "zero"
    selectDivByZero _ = Nothing

순결에 대한 참고 사항

이러한 유형의 예외 처리는 불순한 코드 (예 : IO모나드) 에서만 발생할 수 있습니다 . 순수 코드에서 오류를 처리해야하는 경우 Maybe또는 Either대신 (또는 다른 대수 데이터 유형)을 사용하여 반환 값을 조사해야합니다 . 이것은 종종 더 명시 적이기 때문에 선호되므로 항상 어디서 일어날 수 있는지 알 수 있습니다. 모나드 Control.Monad.Error는 이러한 유형의 오류 처리를 더 쉽게 사용할 수 있도록합니다.


또한보십시오:


8
상당히 유익하지만 Control.Exception 문서에서 경험 법칙을 생략 한 것에 놀랐습니다. 즉 " try비동기 예외에서 복구하지 않는 한을 사용 하십시오.이 경우에는 catch"를 사용하십시오 .
John L


2

나는 또한 당신을 괴롭히는 한 가지는 (당신의 두 번째 질문) 글을 쓰는 :: IO (Either SomeException ())것이고 저도 짜증이납니다.

이제 다음과 같이 일부 코드를 변경했습니다.

let x = 5 `div` 0
result <- try (print x) :: IO (Either SomeException ())
case result of
    Left _ -> putStrLn "Error"
    Right () -> putStrLn "OK"

이에:

let x = 5 `div` 0
result <- try (print x)
case result of
    Left (_ :: SomeException) -> putStrLn "Error"
    Right () -> putStrLn "OK"

이렇게하려면 ScopedTypeVariablesGHC 확장을 사용해야 하지만 미학적으로는 그만한 가치가 있다고 생각합니다.


1

Re : 질문 3 : catch와 handle이 동일합니다 ( hoogle을 통해 찾았습니다 ). 사용할 선택은 일반적으로 각 인수의 길이에 따라 다릅니다. 작업이 더 짧으면 catch를 사용하고 그 반대의 경우도 마찬가지입니다. 문서의 간단한 핸들 예제 :

do handle (\NonTermination -> exitWith (ExitFailure 1)) $ ...

또한 핸들 함수를 커리하여 사용자 정의 핸들러를 만들 수 있습니다. 그런 다음이를 전달할 수 있습니다. (문서에서 수정) :

let handler = handle (\NonTermination -> exitWith (ExitFailure 1))

사용자 지정 오류 메시지 :

do       
    let result = 5 `div` 0
    let handler = (\_ -> print "Error") :: IOException -> IO ()
    catch (print result) handler
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.