Выразительные и составные типы ошибок - PullRequest
18 голосов
/ 30 ноября 2011

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

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

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

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

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

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

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

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

1 Ответ

16 голосов
/ 30 ноября 2011

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

{-# 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 и Расширяемая иерархия исключений с динамической типизацией для получения более подробной информации об использовании этой библиотеки.

...