2010-10-18 12 views
6

¿Cómo se pueden usar excepciones en Haskell sin pasar por IO?Excepciones puras en Haskell

Tengo el siguiente código para insertar un elemento en un árbol de búsqueda binaria con comparaciones mínimas y sin copia cuando el elemento es miembro del árbol. Noté que se utiliza como eithercatch y Left como throw:

insert x t = either (const t) id (insert' x t Nothing) 
    where 
    insert' x E m = maybe (Right (T E x E)) (\v -> if x==v then Left E else Right (T E x E)) m 
    insert' x [email protected](T l v r) m = if x<v 
           then fmap (\l' -> T l' v r) (insert' x l Nothing) 
           else fmap (\r' -> T l v r') (insert' x r (Just v)) 

así que traté de volver a escribir usando Control.Monad.Error la esperanza de hacer el código más simple, pero tengo un gran lío. ¿Alguna sugerencia?

Respuesta

4

El paquete monadLib en Hackage tiene una mónada de excepción (y un transformador de mónada ExceptionT) que puede usar sin IO. Cuando lo ejecutas, obtienes un Either type como resultado.

8

Depende de las excepciones que desee.

Si está intentando devolver un valor de error de una función (por ejemplo, "clave no encontrada" o "la clave ya existe"), entonces debe usar algo en esta línea. "Izquierda" se usa tradicionalmente para el valor de error con el argumento de que "Derecha" es el resultado correcto. La mónada Error se usa de la misma manera aquí que la mónada "Quizás": cuando se produce un error, el resto del cálculo no se realiza, sin tener que encadenar muchos "si es así, si ..." juntos. En este caso, la "excepción" no es realmente excepcional; su código tiene que manejarlo o pasarlo al siguiente nivel de alguna manera.

Por otro lado, es posible que también desee atrapar excepciones imprevistas, como "cabeza []", donde pensó que algo nunca podría suceder, pero estaba equivocado. Debido a que estas excepciones son impredecibles, pueden no ser deterministas y generalmente no se ajustan al sistema de tipos, deben tratarse como eventos IO. El patrón habitual es ignorar estas excepciones, excepto el nivel superior de su programa, donde puede intentar guardar el trabajo del usuario y mostrar un mensaje útil como "informe este error".

Lanzar este último tipo de excepción es fácil: simplemente llame a "error". Pero solo úsela para cosas que realmente cree que no pueden suceder; nunca debe ser una parte normal de tu código.

+0

también echar un vistazo a la [Control de errores] capítulo (http://book.realworldhaskell.org/read/error-handling.html) en el libro _Real Mundial Haskell_. –

+0

Nitpick: si no usas 'Left' para el caso de error, no puedes convertir' Either' en un functor, mónada, etc., que es lo más importante; no son solo dos significados de * derecha *. –

+1

@Antal S-Z: Personalmente, pensé que era solo porque [el lado izquierdo es malo] (http://en.wiktionary.org/wiki/sinister#Etymology). –

2

Tricky! Es un mecanismo realmente bueno para comparar los valores con (==) en el último momento y solo si es necesario. ¿Por qué no lo comentaste al menos con información tipo?

data Tree a = E | T (Tree a) a (Tree a) 

insert :: (Ord a) => a -> Tree a -> Tree a 
insert x t = const t `either` id $ insert' x t Nothing 
    where 
    -- insert' (insert_this) (into_this_empty_tree) (except_if_it_equals_this) (because_then_the_tree_is_Left_unchanged) 
    insert' :: (Ord a) => a -> Tree a -> Maybe a -> Either (Tree a) (Tree a) 
    insert' x E Nothing = Right (T E x E) 
    insert' x E (Just v) | x==v  = Left E 
         | otherwise = Right (T E x E) 
    -- insert' (insert_this) (into_this_nonempty_tree) ((anyway)) (recursive:if_it_branches_to_the_left_insert_it_there) 
    -- insert' (insert_this) (into_this_nonempty_tree) ((anyway)) (recursive:if_it_equals_or_branches_to_the_right_insert_it_there_except_if_the_right_branch_is_empty) 
    insert' x [email protected](T l v r) _ | x<v  = (\l' -> T l' v r) `fmap` insert' x l Nothing 
          | otherwise = (\r' -> T l v r') `fmap` insert' x r (Just v) 

¿Por qué utilizar cualquiera, si tiras el caso Izquierda distancia y luego utilice una copia? Sería más eficiente si no mantienes esa copia para reemplazar un árbol igual y, en cambio, no construyes un árbol igual. De alguna manera como esto ...

insert' :: (Ord a) => a -> Tree a -> Maybe a -> Maybe (Tree a) 

Y entonces ... si quieres ser realmente eficaz, no construir ese (Tal vez un) parámetro sólo para compararlo después.

--insert'1 :: (Ord a) => a -> Tree a -> Nothin -> Maybe (Tree a) 
--insert'2 :: (Ord a) => a -> Tree a -> Just a -> Maybe (Tree a) 
insert'1 :: (Ord a) => a -> Tree a -> Maybe (Tree a) 
insert'2 :: (Ord a) => a -> Tree a -> a -> Maybe (Tree a) 

La solución se vería así:

insert :: (Ord a) => a -> Tree a -> Tree a 
insert x t = fromMaybe t $ insert'1 x t 
    where 
    insert'1 :: (Ord a) => a -> Tree a -> Maybe (Tree a) 
    insert'2 :: (Ord a) => a -> Tree a -> a -> Maybe (Tree a) 
    insert'1 x E = Just (T E x E) 
    insert'1 x (T l v r) | x<v  = do l' <- insert'1 x l 
              Just (T l' v r) 
         | otherwise = do r' <- insert'2 x r 
              Just (T l v r') 
    insert'2 x E v = guard (x/=v) >> Just (T E x E) 
    insert'2 x t _ = insert'1 x t 

(EDITAR :)

En Control.Monad.Error es esta instancia definida:

Error e => MonadError e (Either e) 

Eso significa, que (Either String) es probablemente lo que está buscando.

insert :: (Ord a,MonadError String m) => a -> Tree a -> m (Tree a) 
insert x t = maybe (throwError "Error: element already in tree") return $ insert'1 x t 
    where ... 
+0

En realidad, fui exactamente al revés de 'insert'1' y' insert'2' a 'insert''. Y tienes toda la razón de que podría usar 'Maybe' para el resultado de' insert''. Pero entonces la pregunta será cómo sustituir 'Nothing' con' throw' y 'maybe' con' catch'. –

+1

'if_it_equals_or_branches_to_the_right_insert_it_there_except_if_the_right_branch_is_empty' Este es el identificador más largo que he visto en mi vida. – fuz

Cuestiones relacionadas