2010-12-21 25 views
10

Estaba escribiendo un código rápido, y quería usar el guard function en la IO Monad. Sin embargo, hay no definition of MonadPlus for IO lo que significa que no podemos usar guardia en IO land. He visto an example of using the MabyeT transformer to use guard in the Maybe Monad y luego he levantado todas las acciones IO, pero realmente no quiero hacer eso si no es necesario.MonadPlus definición para Haskell IO

Algunos ejemplos de lo que quiero podrían ser:

handleFlags :: [Flag] -> IO() 
handleFlags flags = do 
    when (Help `elem` flags) (putStrLn "Usage: program_name options...") 
    guard (Help `elem` flags) 
    ... do stuff ... 
    return() 

Me preguntaba si había una manera agradable para conseguir una función de protección (o algo similar) en la mónada IO a través de una declaración de MonadPlus o de otra manera . O quizás lo estoy haciendo mal; ¿Hay alguna forma mejor de escribir ese mensaje de ayuda en la función anterior? Gracias.

(PS pude utilizar las instrucciones if-then-else, pero parece que derrotar el punto de alguna manera. Por no mencionar que para una gran cantidad de opciones que dará lugar a una enorme cantidad de anidación.)

Respuesta

20

considerar la definición de MonadPlus:

class Monad m => MonadPlus m where 
    mzero :: m a 
    mplus :: m a -> m a -> m a 

¿Cómo poner en práctica mzero para IO? Un valor de tipo IO a representa un cálculo IO que devuelve algo del tipo a, por lo que mzero debería ser un cálculo IO que devuelva algo de cualquier tipo posible. Claramente, no hay forma de invocar un valor para algún tipo arbitrario, ya diferencia de Maybe no hay un constructor "vacío" que podamos usar, así que mzero sería necesariamente representaría un cálculo IO que nunca devuelve.

¿Cómo se escribe un cálculo de IO que nunca se devuelve? O ingresas en un bucle infinito o arrojas un error de tiempo de ejecución, básicamente. El primero es de dudosa utilidad, por lo que lo último es lo que le impide.

En resumen, para escribir una instancia de MonadPlus para IO lo que se hace es la siguiente: ¿Ha mzero una excepción de tiempo de ejecución, y tienen mplus evaluar su primer argumento, mientras que la captura de excepciones emitidas por mzero. Si no se generan excepciones, devuelva el resultado. Si se produce una excepción, evalúe el segundo argumento de mplus, mientras ignora las excepciones.

Dicho esto, las excepciones de tiempo de ejecución a menudo se consideran indeseables, por lo que dudaría antes de seguir por ese camino. Si desea hacerlo de esa manera (y no le importa aumentar las posibilidades de que su programa se bloquee en tiempo de ejecución) encontrará todo lo que necesita para implementar lo anterior en Control.Exception.

En la práctica, probablemente utilizaría el enfoque del transformador de mónada si quisiera un montón de guard en el resultado de evaluar expresiones monádicas, o si la mayoría de los condicionales dependen de valores puros provistos como argumentos de función (que las banderas en su ejemplo son) use las protecciones de patrones como en la respuesta de @ Anthony.

+0

Esa es una respuesta excelente y me permite entender por qué MonadPlus para IO realmente hace sin sentido sin errores o bucles infinitos. –

+1

@Robert Massaioli: Lo cual no quiere decir que haya algo * incorrecto * con eso. Es una instancia 'MonadPlus' perfectamente correcta, es equivalente a cómo funciona 'MonadPlus' para usar' Either e' como una mónada de error, y creo que una instancia como 'IO' incluso existe en una de las bibliotecas de transformadores de mónada. El único problema es que tienes que ser mucho más cuidadoso con los errores de captura, porque a diferencia de 'MaybeT' o similar, las excepciones pueden escaparse hasta 'main'. –

8

hago este tipo de cosas con guardias.

handleFlags :: [Flag] -> IO() 
handleFlags flags 
    | Help `elem` flags = putStrLn "Usage: program_name options..." 
    | otherwise = return() 
+2

A veces se sabe que son a la derecha rastrear (usando guardias) pero luego alguien señala la idea correcta (esto) y usted tiene un momento de palmadas en la frente. Gracias, usé esto y es exactamente lo que quería. Aunque marqué camccann como la respuesta porque él mostró por qué mi idea era mala, cuáles son mis alternativas y luego señaló a la tuya como lo que habría hecho. –

+1

Tenga en cuenta que hay bibliotecas como cmdargs que se ocupan de tales cosas para usted :) –

+0

fwiw, cmdargs tiene demasiada magia para mi gusto. Prefiero parseargs porque todo está por encima de la tabla. – sclv

0

Hay funciones hechas precisamente para esto: en Control.Monad, las funciones when y su contraparte unless.respuesta de Anthony se puede reescribir como tales:

handleFlags :: [Flag] -> IO() 
handleFlags flags = 
    when (Help `elem` flags) $ putStrLn "Usage: program_name options..." 

especificaciones:

when :: (Applicative m) => Bool -> m() -> m() 
unless bool = when (not bool) 

Link to docs on hackage.haskell.org

Si se necesita más, aquí hay un link to another package, specifically monad-oriented and with several more utilities: Control.Monad.IfElse