Pregunta

Tengo algunas funciones escritas en C que llamo de Haskell. Estas funciones devuelven IO (CInt). A veces quiero ejecutar todas las funciones con independencia de lo que cualquiera de ellos regresan, y esto es fácil. En aras de código de ejemplo, esta es la idea general de lo que está pasando actualmente:

Prelude> let f x = print x >> return x
Prelude> mapM_ f [0..5]
0
1
2
3
4
5
Prelude>

consigo mis efectos secundarios deseados, y no me importa acerca de los resultados. Pero ahora tengo que detener la ejecución inmediatamente después de que el primer elemento que no me devuelve el resultado deseado. Digamos que un valor de retorno de 4 o superior requiere la ejecución se pare - entonces lo que que desee a hacer es lo siguiente:

Prelude> takeWhile (<4) $ mapM f [0..5]

Lo que me da este error:

<interactive>:1:22:
    Couldn't match expected type `[b]' against inferred type `IO a'
    In the first argument of `mapM', namely `f'
    In the second argument of `($)', namely `mapM f ([0 .. 5])'
    In the expression: takeWhile (< 4) $ mapM f ([0 .. 5])

Y eso tiene sentido para mí - el resultado todavía está contenida en la mónada IO, y no puede simplemente comparar dos valores contenidos en la mónada IO. Sé que esto es precisamente el propósito de mónadas - encadenar resultados juntos y operaciones de descarte cuando se cumple una determinada condición - pero ¿hay una manera fácil de "terminar" la mónada IO en este caso para detener la ejecución de la cadena de una condición de mi elección, sin necesidad de escribir una instancia de MonadPlus?

¿Puedo simplemente "unlift" los valores de f, a los efectos de la takeWhile?

Es esto una solución en la que encajan funtores? Funtores no han "hecho clic en" conmigo todavía, pero yo tipo de tener la impresión de que esto podría ser una buena situación para usarlos.


Actualización:

@sth tiene la respuesta más cerca de lo que quiero - de hecho, eso es casi exactamente lo que iba a, pero todavía me gustaría ver si hay un solución estándar que ISN' t explícitamente recursiva - esto es Haskell, después de todo! Mirando hacia atrás en cómo redactado mi pregunta, ahora puedo ver que yo no era lo suficientemente claro sobre mi comportamiento deseado.

La función f utilicé anteriormente para un ejemplo fue simplemente un ejemplo. Las funciones reales están escritas en C y utilizados exclusivamente por sus efectos secundarios. No puedo utilizar @ sugerencia de mapM_ f (takeWhile (<4) [0..5]) de Tom porque no tengo ni idea de si cualquier entrada será realmente como resultado el éxito o el fracaso hasta que sea ejecutada.

En realidad no importa la lista devuelta, ya sea - sólo quiero llamar a las funciones C hasta que se agote la lista o la primera función C devuelve un código de error

.

En pseudocódigo C-estilo, mi comportamiento sería:

do {
    result = function_with_side_effects(input_list[index++]);
} while (result == success && index < max_index);

Así que de nuevo, la respuesta de @ sth realiza el comportamiento exacto que quiero, a excepción de que los resultados pueden (deben?) Se descartarán. Una función dropWhileM_ sería equivalente para mis propósitos. ¿Por qué no hay una función así o takeWhileM_ en Control.Monad? Veo que no había una discusión similar en una lista de correo , pero parece que nada ha llegado de ello.

¿Fue útil?

Solución

Es posible definir secuencia como

sequence xs = foldr (liftM2 (:)) (return []) xs

El problema con liftM2 que ha estado viendo es que usted no tiene la oportunidad de detener m2, lo que podría ser launchTheMissiles!

liftM2 :: (Monad m) => (a -> b -> c) -> m a -> m b -> m c
liftM2 f m1 m2 = do
    x1 <- m1
    x2 <- m2
    return (f x1 x2)

Uso guard como en el siguiente parece atractiva:

sequenceUntil p xs = foldr (myLiftM2 p (:)) (return []) xs
  where myLiftM2 p f m1 m2 = do
            x1 <- m1
            guard $ p x1
            x2 <- m2
            return (f x1 x2)

El código anterior se producirá un error en su aplicación debido a la mónada IO no es una instancia de MonadPlus .

Así que mantenga su mano un poco más

module Main where

import Control.Monad

printx :: Int -> IO Int
printx x = do
    print x
    return x

sequenceUntil :: (Monad m) => (a -> Bool) -> [m a] -> m [a]
sequenceUntil p xs = foldr (myLiftM2 (:) []) (return []) xs
  where myLiftM2 f z m1 m2 = do
            x1 <- m1
            if p x1 then do x2 <- m2
                            return $ f x1 x2
                    else return z

main :: IO ()
main = do
  let as :: [IO Int]
      as = map printx [1..10]
  ys <- sequenceUntil (< 4) as
  print ys

A pesar de que as es una lista de acciones a lo largo del 1 al 10, la salida es

1
2
3
4
[1,2,3]

Descartando los resultados es entonces trivial:

sequenceUntil_ :: (Monad m) => (a -> Bool) -> [m a] -> m ()
sequenceUntil_ p xs = sequenceUntil p xs >> return ()

main :: IO ()
main = do
  let as :: [IO Int]
      as = map printx [1..]
  sequenceUntil_ (< 4) as

Tenga en cuenta el uso de [1..] que muestra la nueva rel="noreferrer"> combinador .


Es posible que prefiera spanM:

spanM :: (Monad m) => (a -> Bool) -> [m a] -> m ([a], [m a])
spanM _ [] = return ([], [])
spanM p (a:as) = do
  x <- a
  if p x then do (xs,bs) <- spanM p as
                 return (x:xs, bs)
         else return ([x], as)

Tenga en cuenta que difiere ligeramente de lapso en que incluye el elemento de fracasar en la lista de resultados. segundo de la pareja son las acciones restantes. Por ejemplo:

*Main> (xs,bs) <- spanM (< 4) as
1
2
3
4
*Main> xs  
[1,2,3,4]
*Main> sequence bs
5
6
7
8
9
10
[5,6,7,8,9,10]

Sin embargo, otra alternativa:

untilM :: Monad m => (a -> Bool) -> [m a] -> m ()
untilM p (x:xs) = do
  y <- x
  unless (p y) $ untilM p xs

Tenga en cuenta que el sentido del predicado se complementa:

*Main> untilM (>= 4) as
1
2
3
4

Otros consejos

No creo que haya nada como un takeWhileM en la biblioteca estándar, pero se puede escribir por sí mismo de manera que sólo la cantidad de IO, según sea necesario se ejecuta:

takeWhileM :: (Monad m) => (a -> Bool) -> [m a] -> m [a]
takeWhileM _ [] = return []
takeWhileM p (a:as) =
   do v <- a
      if p v
         then do vs <- takeWhileM p as
                 return (v:vs)
         else return []

La lista suministrada sólo se evalúa hasta que se encuentre un elemento, que no coincide con el predicado:

*Main> takeWhileM (<4) (map f [1..5])
1
2
3
4
[1,2,3]

Editar:. Ahora veo lo que estás buscando

gbacon registró una función sequenceWhile agradable, que es casi lo "primitivo" que necesita.

En realidad, ya que sólo está interesado en los efectos secundarios, sequenceWhile_ debería ser suficiente. He aquí una definición (de nuevo, inspirado en gbacon, votarle arriba!):

sequenceWhile_ :: (Monad m) => (a -> Bool) -> [m a] -> m ()
sequenceWhile_ p xs = foldr (\mx my -> mx >>= \x -> when (p x) my)
                            (return ()) xs

Se llama a este modo:

Prelude Control.Monad> sequenceWhile (<4) $ map f [1..]

Respuesta original:

No se puede simplemente "unlift" los valores de la Mónada IO para su uso con takeWile, pero se puede takeWhile "levantar" para su uso dentro de una mónada!

El liftM función tomará una (a -> b) función a una función (m a -> m b), donde m es una mónada.

(Como nota al margen, se puede encontrar una función como ésta mediante la búsqueda de su tipo en Hoogle , en este caso mediante la búsqueda de: Monad m => (a -> b) -> (m a -> m b) )

Con liftM usted puede hacer esto:

Prelude> :m + Control.Monad
Prelude Control.Monad> let f x = print x >> return x
Prelude Control.Monad> liftM (takeWhile (<4)) $ mapM f [0..5]
0
1
2
3
4
5
[0,1,2,3]

Ahora, esto podría no ser lo que quería. El mapM se aplicará la función f a toda la lista en secuencia, antes de retornar una lista. Esa lista resultante se pasa a la función takeWhile levantada.

Si desea detener la impresión después de que el tercer elemento, que tendrá que dejar de llamar a la impresión. Eso significa, no se aplican f a un elemento tal. Así pues, usted va a terminar con algo tan simple como:

Prelude> mapM_ f (takeWhile (<4) [0..5])

Por cierto, en caso de que se preguntan ¿Por qué mapM imprimirá primero de todo, antes de regresar a la lista. Esto se puede ver mediante la sustitución de las funciones con sus definiciones:

mapM f [0..1]
=
sequence (map f [0..1])
=
sequence (f 0 : map f [1..1])
=
sequence (f 0 : f 1 : [])
=
sequence ((print 0 >> return 0) : f 1 : [])
= 
sequence ((print 0 >> return 0) : (print 1 >> return 1) : [])
=
do x  <- (print 0 >> return 0)
   xs <- (sequence ((print 1 >> return 1) : []))
   return (x:xs)
=
do x  <- (print 0 >> return 0)
   xs <- (do y  <- (print 1 >> return 1)
             ys <- sequence ([])
             return (y:ys))
   return (x:xs)
=
do x  <- (print 0 >> return 0)
   xs <- (do y  <- (print 1 >> return 1)
             ys <- return []
             return (y:ys))
   return (x:xs)
=
do x  <- (print 0 >> return 0)
   xs <- (do y <- (print 1 >> return 1)
             return (y:[]))
   return (x:xs)
=
do x  <- (print 0 >> return 0)
   xs <- (print 1 >> return (1:[]))
   return (x:xs)
=
do x <- (print 0 >> return 0)
   print 1
   return (x:1:[])
=
do print 0
   print 1
   return (0:1:[])

Este proceso de sustitución de funciones con sus definiciones se llama razonamiento ecuacional .

Si yo no he cometido ningún error, ahora se puede (con suerte) ver que mapM (usando sequence) imprime primero de todo, y después devuelve una lista.

Puede utilizar la una de la "Lista" paquete .

import Control.Monad.ListT (ListT)
import Data.List.Class (execute, fromList, joinM, takeWhile)
import Prelude hiding (takeWhile)

f x = print x >> return x
main =
  execute . takeWhile (< 4) .
  joinM $ fmap f (fromList [0..5] :: ListT IO Int)
  • fromList [0..5] crea una lista que contiene monádico 0..5, que no realiza ninguna acción monádicos
  • fmap f a esa lista se traduce en una ListT IO (IO Int) que todavía no realiza ninguna acción monádicos, solo contiene queridos.
  • joinM convierte eso en una ListT IO Int. cada acción contenida conseguiría ejecuta cuando se consume el artículo y su resultado será el valor de la lista.
  • takeWhile se generaliza para cualquier List. Tanto [] y "Monad m => ListT m" son instancias de List.
  • execute consume la lista monádico, la ejecución de todas sus acciones.
  • En caso de estar interesado en los resultados que se pueden utilizar "toList :: List m => m a -> ItemM m [a]" ( "ItemM (ListT IO)" es IO). por lo que en este caso es "toList :: ListT IO a -> IO [a]". Mejor aún puede seguir usando funciones de orden superior tales como scanl, etc para procesar la lista monádico ya que está siendo ejecutado.

Más recientemente, se puede utilizar el MonadList hackage que incluye funciones útiles nofollow como takeWhileM, dropWhileM, y deleteByM muchos más.

Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top