하스켈 학습 노트: Exception, Except, ExceptT Monad 변환기

Exception 클래스

class (Typeable e, Show e) => Exception e where
    toException   :: e -> SomeException
    fromException :: SomeException -> Maybe e

    toException = SomeException
    fromException (SomeException e) = cast e

    displayException :: e -> String
    displayException = show

instance Exception SomeException where
    toException se = se
    fromException = Just
    displayException (SomeException e) = displayException e

ExceptT Monad 변환기

newtype ExceptT e m a = ExceptT (m (Either e a))

instance (Monad m) => Monad (ExceptT e m) where
    return a = ExceptT $ return (Right a)
    m >>= k = ExceptT $ do
        a <- runExceptT m
        case a of
            Left err -> return (Left err)
            Right val -> runExceptT (k val)

runExceptT :: ExceptT e m a -> m (Either e a)
runExceptT (ExceptT m) = m

  • newtype ExceptT e m a = ExceptT (m (Either e a)) ExceptT 타입은 기존 타입을 감싸는 newtype입니다. 이 타입에는 두 개의 타입 매개변수가 있습니다: 내부 Monad 타입 m과 기본 Monad Either의 매개변수 타입 e와 a입니다. ExceptT e m a는 m (Either e a) 타입의 값을 감싸며, runExceptT 함수를 통해 이 값을 추출할 수 있습니다.
  • instance (Monad m) => Monad (ExceptT e m) where 만약 m이 Monad라면, ExceptT e m도 Monad입니다. Monad 타입 클래스의 정의와 비교하면, return 함수의 타입 서명은 다음과 같습니다: return :: a -> ExceptT e m a 대략 a -> m (Either e a)와 동일합니다 bind 연산자의 타입 서명은 다음과 같습니다: (>>=) :: ExceptT e m a -> (a -> ExceptT e m b) -> ExceptT e m b 대략 m (Either e a) -> (a -> m (Either e b)) -> m (Either e b)와 동일합니다
  • return a = ExceptT $ return (Right a) return 함수는 먼저 Right a를 내부 Monad m에 감싼 다음, 이를 ExceptT Monad 변환기에 감싸서 반환합니다. 여기서 왼쪽의 return은 ExceptT Monad의 return이며, 오른쪽의 return은 내부 Monad m의 return입니다.
  • m >>= k = ExceptT $ do 함수 서명과 비교하면, m의 타입은 ExceptT e m a이며, 대략 m (Either e a)와 동일합니다 k의 타입은 a -> ExceptT e m b이며, 대략 a -> m (Either e b)와 동일합니다
  • a <- runExceptT m m의 타입과 비교하면, a의 타입은 Either e a입니다 이는 runMaybeT 함수가 m을 ExceptT Monad에서 분리하고, <- 연산자가 runExceptT m을 내부 Monad m에서 분리하기 때문입니다.
  • case a of
  • Left err -> return (Left err) 여기서 return은 내부 Monad m의 return이므로, return (Left err)의 타입은 m (Either e a)입니다.
  • Right val -> runExceptT (k val) k의 타입은 a -> ExceptT e m b이므로, k val의 타입은 ExceptT e m b입니다 runExceptT (k val)의 타입은 m (Either e b)입니다
ExceptT e m가 Monad 법칙을 따르는지 증명합니다.
1. return a >>= f ≡ f a
return a >>= f
≡ (ExceptT $ return (Right a)) >>= f
≡ ExceptT (m (Right a)) >>= f
≡ ExceptT $ runExceptT (f a)
≡ f a
2. m >>= return ≡ m
ExceptT (m (Right x)) >>= return
≡ ExceptT $ runExceptT (return x)
≡ ExceptT (m (Right x))
ExceptT (m (Left err)) >>= return
≡ ExceptT $ runExceptT (return (Left err))
≡ ExceptT (m (Left err))
3. (m >>= f) >>= g ≡ m >>= (\x -> f x >>= g)
(ExceptT (m (Right x)) >>= f) >>= g ≡ f x >>= g
(ExceptT (m (Left err)) >>= f) >>= g ≡ ExceptT (m (Left err)) >>= g ≡ ExceptT (m (Left err))
ExceptT (m (Right x) >>= (\x -> f x >>= g) ≡ (\x -> f x >>= g) x ≡ f x >>= g
ExceptT (m (Left err)) >>= (\x -> f x >>= g) ≡ ExceptT (m (Left err))

lift, liftIO 함수

instance MonadTrans (ExceptT e) where
    lift = ExceptT . liftM Right

instance (MonadIO m) => MonadIO (ExceptT e m) where
    liftIO = lift . liftIO

MaybeT에서 lift 함수의 정의가 lift 법칙을 따르는지 증명합니다.
1. lift . return ≡ return
lift . return $ a
≡ ExceptT . liftM Right . return $ a
≡ ExceptT (m (Right a))
≡ return a
2. lift (m >>= f) ≡ lift m >>= (lift . f)
m = n a이고 f a = n b라고 가정
그러면 m >>= f = n b
lift (m >>= f)
≡ ExceptT . liftM Right $ m >>= f
≡ ExceptT . liftM Right $ n b
≡ ExceptT (n (Right b))
lift m >>= (lift . f)
≡ (ExceptT . liftM Right $ m) >>= (ExceptT . liftM Right . f)
≡ (ExceptT (n (Right a))) >>= (\x -> ExceptT . liftM Right . f $ x)
≡ ExceptT $ runExceptT $ ExceptT . liftM Right . f $ a
≡ ExceptT $ runExceptT $ ExceptT . liftM Right $ n b
≡ ExceptT $ runExceptT $ ExceptT (n (Right b))
≡ ExceptT (n (Right b))

Except Monad

type Except e = ExceptT e Identity

Except Monad는 ExceptT Monad 변환기의 특수한 경우입니다.

실용 예제

-- https://stackoverflow.com/questions/26385809/catch-someexception-with-exceptt
-- cabal install lifted-base

import Control.Exception.Lifted
import Control.Monad.Trans.Except

errorProneFunction :: ExceptT SomeException IO ()
errorProneFunction = throw DivideByZero

handleError
  :: ExceptT SomeException IO a
  -> ExceptT SomeException IO a
handleError computation = do
  result <- try $ computation
  case result of
    Right value -> return value
    Left exception -> throwE exception

handleError'
  :: ExceptT SomeException IO a
  -> ExceptT SomeException IO a
handleError' = handle throwE

main :: IO ()
main = do
    outcome <- runExceptT $ handleError errorProneFunction
    case outcome of Left _ -> putStrLn "오류가 포착되었습니다"
                   Right _ -> putStrLn "아니요, 오류가 포착되지 않았습니다" 

태그: Haskell Monad Transformer Exception Handling Functional Programming Type Classes

7월 1일 20:52에 게시됨