2011-11-30 17 views
18

Estoy luchando con la mejor manera de informar errores en un conjunto de funciones que deberían componer muy bien, en una biblioteca en la que estoy trabajando.Tipos de error expresivo y composable

Concretamente, me tienen funciones que se parecen:

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

donde foo se puede dejar en un solo sentido (un buen ajuste para Maybe), pero bar y baz puede fallar en dos formas diferentes, cada uno (buenos ajustes para Either BarErrors y Either BazErrors).

Una solución es crear:

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

y hacer todas las funciones devuelven Either AllTheErrors, que expresa la gama de errores que pueden ser planteadas por una secuencia compuesta de estas funciones a expensas de expresar la rango de errores posible para cada función individual.

¿Hay alguna manera de obtener ambos? Tal vez con algo más que la composición monádica? O con familias tipo (manos de las olas) ...?

Respuesta

16

La biblioteca Control.Monad.Exception permite que se utilicen excepciones fuertemente tipadas en el código no IO. Esto permite que las funciones arrojen errores y comprendan fácilmente con funciones que generan errores diferentes. Por ejemplo:

{-# 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" 

Ver también los documentos Explicitly Typed Exceptions for Haskell y An Extensible Dynamically-Typed Hierarchy of Exceptions para más detalles sobre el uso de esta biblioteca.

+0

¡Brillante, gracias! – jberryman

+0

Después de investigar un poco, lo que creo que se adaptará a mi biblioteca es definir polimórficamente las funciones de mi biblioteca en la clase 'Failure' del paquete 'failure', aquí: http://hackage.haskell.org/package/failure. Eso me permite expresar los tipos de excepciones que pueden surgir en el tipo sig, y les da a mis usuarios la opción de usar algo simple como 'Maybe', o algo más robusto como control-monad-exception (que proporciona una instancia). Gracias de nuevo. – jberryman