Подтвердить что ты не робот

Выразительные и составные типы ошибок

Я борюсь с лучшим способом сообщать об ошибках в наборе функций, которые должны составляться красиво, в библиотеке, над которой я работаю.

Конкретно, у меня есть функции, которые выглядят так:

foo, bar, baz :: a -> Maybe a

где foo может выйти только одним способом (подходит для Maybe), но bar и baz могут терпеть неудачу двумя разными способами (хорошо подходит для Either BarErrors и Either BazErrors),

Одним из решений является создание:

data AllTheErrors = TheFooError
                  | BarOutOfBeer
                  | BarBurnedDown
                  | ...

и вернуть все функции Either AllTheErrors, которые выражают диапазон ошибок, которые могут быть вызваны составленной последовательностью этих функций за счет выражения диапазона ошибок, возможных для каждой отдельной функции.

Есть ли способ получить оба? Может быть, с чем-то другим, кроме монадического состава? Или с типами семейств (волны рук)...?

4b9b3361

Ответ 1

Библиотека Control.Monad.Exception позволяет использовать строго типизированные исключения для использования в коде, отличном от IO. Это позволяет функциям бросать ошибки и легко компоноваться с функциями, которые вызывают разные ошибки. Например:

{-# LANGUAGE RankNTypes, MultiParamTypeClasses, FunctionalDependencies #-}
{-# LANGUAGE FlexibleInstances #-}
import Prelude hiding (catch)
import Control.Monad.Exception


data FooException = FooException deriving (Show, Typeable)
instance Exception FooException

data BarErrors = BarErrors deriving (Show, Typeable)
instance Exception BarErrors

data BazErrors = BazErrors deriving (Show, Typeable)
instance Exception BazErrors

-- sample functions    
foo :: (Throws FooException l) => a -> EM l a
foo a = return a


bar :: (Throws BarErrors l) => a -> EM l a
bar _ = throw BarErrors

baz :: (Throws BazErrors l) => a -> EM l a
baz a = return a


-- using all at once:

allAtOnce :: (Throws FooException l, Throws BarErrors l, Throws BazErrors l) =>
             a -> EM l String
allAtOnce x = do
  _ <- foo x
  _ <- bar x
  _ <- baz x
  return "success!"

-- now running the code, catching the exceptions:

run :: a -> String
run x = runEM $ allAtOnce x `catch` (\(_ :: FooException) -> return "foo failed")
        `catch` (\BarErrors -> return "bar failed")
        `catch` (\BazErrors -> return "baz failed")


-- run 3 results in "bar failed"

См. также статьи Явно типизированные исключения для Haskell и Расширяемая динамически типизированная иерархия исключений для более подробную информацию об использовании этой библиотеки.