opciones - Haskell: ¿Por qué los tipos Maybe y Either se comportan de manera diferente cuando se usan como Monads?
imprimir en haskell (2)
Estoy tratando de entender el manejo de errores en Haskell. Encontré el artículo " 8 formas de informar errores en Haskell " pero estoy confundido sobre por qué Maybe y Oither se comportan de manera diferente.
Por ejemplo:
import Control.Monad.Error
myDiv :: (Monad m) => Float -> Float -> m Float
myDiv x 0 = fail "My divison by zero"
myDiv x y = return (x / y)
testMyDiv1 :: Float -> Float -> String
testMyDiv1 x y =
case myDiv x y of
Left e -> e
Right r -> show r
testMyDiv2 :: Float -> Float -> String
testMyDiv2 x y =
case myDiv x y of
Nothing -> "An error"
Just r -> show r
Llamar a testMyDiv2 1 0
da un resultado de "An error"
, pero al llamar a testMyDiv1 1 0
obtiene:
"*** Exception: My divison by zero
(Tenga en cuenta la falta de cotización de cierre, lo que indica que esto no es una cadena sino una excepción).
¿Lo que da?
Supongo que estás usando monads-fd
.
$ ghci t.hs -hide-package mtl
*Main Data.List> testMyDiv1 1 0
"*** Exception: My divison by zero
*Main Data.List> :i Either
...
instance Monad (Either e) -- Defined in Control.Monad.Trans.Error
...
Mirando en el paquete de transformadores , que es donde monads-fd
obtiene la instancia, vemos:
instance Monad (Either e) where
return = Right
Left l >>= _ = Left l
Right r >>= k = k r
Entonces, no hay definición para Fail what-so-ever. En general, se desaconseja el fail
, ya que no siempre se garantiza que fallará limpiamente en una mónada (a mucha gente le gustaría ver que se elimine el fail
de la clase Monad).
EDITAR: debería agregar que ciertamente no está claro el fail
fue intencionado que se dejara como la llamada de error
predeterminada. Un ping a haskell-cafe o al mantenedor puede valer la pena.
EDIT2: La instancia mtl
se ha movido a la base , este movimiento incluye eliminar la definición de fail = Left
y discusión sobre por qué se tomó esa decisión. Presumiblemente, quieren que las personas usen ErrorT más cuando las mónadas fallan, reservando así fail
para situaciones más catastróficas como coincidencias de patrones incorrectos (ej .: Just x <- e
donde e -->* m Nothing
).
La respuesta corta es que la clase Monad en Haskell agrega la operación fail
a la idea matemática original de las mónadas, lo que hace que sea un tanto controvertido cómo hacer que cualquiera de los tipos se convierta en una Monad
(Haskell), porque hay muchas formas de hacerlo.
Hay varias implementaciones flotando que hacen cosas diferentes. Los 3 enfoques básicos que conozco son:
-
fail = Left
. Esto parece ser lo que la mayoría de la gente espera, pero en realidad no se puede hacer en el estricto Haskell 98. La instancia tendría que declararse comoinstance Monad (Either String)
, que no es legal en H98 porque menciona un tipo particular para uno de losEither
parámetros (en GHC, la extensión FlexibleInstances haría que el compilador la aceptara). - Ignore
fail
, utilizando la implementación predeterminada que solo llama aerror
. Esto es lo que está sucediendo en tu ejemplo. Esta versión tiene la ventaja de ser compatible con H98, pero la desventaja de ser bastante sorprendente para el usuario (con la sorpresa en el tiempo de ejecución). - La implementación de
fail
llama a otra clase para convertir una cadena en cualquier tipo. Esto se hace en el móduloControl.Monad.Error
de MTL, que declara lainstance Error e => Monad (Either e)
. En esta implementación,fail msg = Left (strMsg msg)
. Este es nuevamente legal H98, y de vez en cuando sorprende a los usuarios porque introduce otra clase de tipo. Sin embargo, a diferencia del último ejemplo, la sorpresa llega en el momento de la compilación.