Así que, usted Está atascado dentro de IO
, y quiere verificar un montón de condiciones sin muchas anotaciones if
s. Espero que me perdones una digresión sobre la resolución de problemas más generales en Haskell a modo de respuesta.
Considere en forma resumida cómo debe comportarse esto. Comprobación de una condición tiene uno de dos resultados:
- éxito, en cuyo caso el programa se ejecuta el resto de la función
- Si no, en cuyo caso el programa descarta el resto de la función y devuelve el mensaje de error.
La comprobación de múltiples condiciones se puede ver de forma recursiva; cada vez que ejecuta "el resto de la función", golpea la siguiente condición, hasta llegar al paso final que simplemente devuelve el resultado. Ahora, como primer paso para resolver el problema, desglosémoslo usando esa estructura, así que, básicamente, queremos convertir un montón de condiciones arbitrarias en partes que podamos componer juntas en una función multi-condicional. ¿Qué podemos concluir sobre la naturaleza de estas piezas?
1) Cada pieza puede devolver uno de dos tipos diferentes; un mensaje de error o el resultado del siguiente paso.
2) Cada pieza debe decidir si se ejecuta el siguiente paso, por lo tanto, al combinar los pasos, debemos asignarle la función que representa el siguiente paso como argumento.
3) Dado que cada pieza espera que se le dé el siguiente paso, para preservar la estructura uniforme necesitamos una manera de convertir el paso final, incondicional en algo que se ve igual a un paso condicional.
El primer requisito obviamente sugiere que querremos un tipo como Either String a
para nuestros resultados. Ahora necesitamos una función de combinación para ajustarse al segundo requisito y una función de envoltura para el tercero. Además, al combinar los pasos, es posible que deseemos tener acceso a los datos de un paso anterior (por ejemplo, validar dos entradas diferentes y luego verificar si son iguales), por lo que cada paso deberá tomar el resultado del paso anterior como argumento.
Por lo tanto, llamando al tipo de cada paso err a
como una forma abreviada, ¿qué tipos podrían tener las otras funciones?
combineSteps :: err a -> (a -> err b) -> err b
wrapFinalStep :: a -> err a
Ahora bien, esas firmas de tipos parecen extrañamente familiar, ¿verdad?
Esta estrategia general de "ejecutar un cálculo que puede fallar temprano con un mensaje de error" de hecho se presta a una implementación monádica; y de hecho, el mtl package ya tiene uno. Más importante aún para este caso, también tiene un transformador de mónada, lo que significa que puede agregar la estructura de mónada de error a otra mónada, como IO
.
lo tanto, sólo puede importar el módulo, hacer un sinónimo de tipo para envolver IO
en una cálida difusa ErrorT
, y ya está:
import Control.Monad.Error
type EIO a = ErrorT String IO a
assert pred err = if pred then return() else throwError err
askUser prompt = do
liftIO $ putStr prompt
liftIO getLine
main :: IO (Either String())
main = runErrorT test
test :: EIO()
test = do
x1 <- askUser "Please enter anything but the number 5: "
assert (x1 /= "5") "Entered 5"
x2 <- askUser "Please enter a capital letter Z: "
assert (x2 == "Z") "Didn't enter Z"
x3 <- askUser "Please enter the same thing you entered for the first question: "
assert (x3 == x1) $ "Didn't enter " ++ x1
return() -- superfluous, here to make the final result more explicit
El resultado de ejecutar test
, como era de esperar, es Right()
para el éxito, o Left String
para el fracaso, donde el String
es el mensaje apropiado; y si un assert
devuelve una falla, no se realizará ninguna de las siguientes acciones.
Para probar el resultado de las acciones IO
puede que le resulte más fácil de escribir una función de ayuda similar a assert
que en vez toma un argumento de IO Bool
, o algún otro método.
También tenga en cuenta el uso de liftIO
para convertir IO
acciones en valores en EIO
y runErrorT
para ejecutar una acción EIO
y devolver el valor Either String a
con el resultado global. Puede leer en monad transformers si desea más detalles.
Muy buena respuesta! –
¡Me encanta el error de mónada! También es ideal para silenciar los mensajes de error falsos en los compiladores ... +1 –
@Norman Ramsey: ¡Bastante bien! En mis primeras aventuras con Haskell, al conocer las bibliotecas que usaban 'Oither' para los errores, rápidamente me molestaba la torpeza de extraer de 'Right' para transmitir los valores; así que escribí funciones que (en retrospectiva) eran casi equivalentes a 'fmap' y' (>> =) '. Casi al mismo tiempo que obtuve el conocimiento para reconocer la estructura monádica, descubrí que 'MonadError' existió todo el tiempo, y me sentí algo tonto por mis crudas reinvenciones.En todo el alboroto sobre las mónadas "sofisticadas", parece que la elegancia de 'Oither' y 'Maybe' monádica se olvida ... –