Какую монаду использовать в Haskell для агрегирования исключений, которые могут возникнуть при выполнении последовательности операторов? - PullRequest
4 голосов
/ 04 марта 2012

Я ищу наиболее распространенный способ сделать что-то вроде:

x :: IO ((),[SomeException])
x = do
  void y
  void z

агрегирует исключения, которые могут быть сгенерированы y и z, и возвращает их как часть типа x.

Есть ли для этого известная монада / учебник?

Ответы [ 3 ]

4 голосов
/ 04 марта 2012

Если y выдает исключение, то вы никогда не достигнете z.Аналогично, если z выдает исключение, это означает, что y не выдало исключение.Таким образом, типичный способ отследить исключения - это просто отследить одно - выброшенное - с ErrorT.

x :: ErrorT SomeException IO Foo
x = do
  a <- y
  b <- z
  return $ f a b

useX :: IO Quux
useX = do
  errOrVal <- runErrorT x
  case errOrVal of
    Left err -> logError err >> quux1
    Right v  -> quux2 v

Вот предположения типа, которые я использовал:*

4 голосов
/ 04 марта 2012

Важной особенностью исключений является то, что если действие IO a выдает исключение, вы не получите никакого результирующего значения a.Поскольку оператор связывания (>>=) :: Monad m => m a -> (a -> m b) -> m b монад позволяет последующим действиям зависеть от результатов более ранних, это означает, что мы не можем выполнить следующие действия, если одно из них завершилось неудачей.можно использовать подход рампиона .Однако из вашего примера кажется, что вы заботитесь только о последовательности независимых действий с помощью оператора (>>).Вы можете сделать моноид из этого, но я думаю, что самый простой подход - это просто иметь функцию, которая запускает список IO () действий и собирает любые исключения в списке:

import Control.Exception (SomeException, try)
import Data.Either (lefts)

exceptions :: [IO ()] -> IO [SomeException]
exceptions = fmap lefts . mapM try

Вы будетенеобходимо использовать список действий вместо do, хотя:

> :{
| exceptions [ putStrLn "foo"
|            , throwIO DivideByZero
|            , putStrLn "bar"
|            , throwIO (IndexOutOfBounds "xyzzy")
|            ]
| :}
foo
bar
[divide by zero,array index out of range: xyzzy]
4 голосов
/ 04 марта 2012

Итак, важный вопрос здесь augustss - «Если y выдает исключение, каким будет значение a

Если у вас есть значения по умолчанию для a и b, вы можете использовать try, чтобы перехватить ваши исключения и агрегировать их, используя WriterT:

x :: IO (C, [SomeException])
x = runWriterT $ do 
  a <- rescue defaultA y 
  b <- rescue defaultB z 
  return $ f a b

rescue :: a -> IO a -> WriterT [SomeException] IO a
rescue a m = do
  res <- lift $ try m
  case res of
    Left e -> do
      tell [e]
      return a
    Right a' -> return a'

data A
data B
data C

y :: IO A
y = undefined

defaultA :: A
defaultA = undefined

z :: IO B
z = undefined

defaultB :: B
defaultB = undefined

f :: A -> B -> C
f = undefined

Без значений по умолчанию вы не можете спасти исключения и продолжить вычисления.

...