Domanda

Ho alcune funzioni scritte in C, che io chiamo da Haskell. Queste funzioni restituiscono IO (CInt). A volte vorrei correre tutte le funzioni indipendentemente da ciò che nessuno di loro ritorno, e questo è facile. Per fare un esempio di codice, questa è l'idea generale di ciò che sta succedendo attualmente:

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

ottengo i miei effetti collaterali desiderati, e non mi importa dei risultati. Ma ora ho bisogno di interrompere l'esecuzione subito dopo il primo elemento che non restituisce il risultato desiderato. Diciamo che un valore di ritorno di 4 o superiore richiede l'esecuzione di fermarsi - poi quello che I voler di fare è questo:

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

Il che mi dà questo errore:

<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])

E questo ha un senso per me - il risultato è ancora contenuto nella monade IO, e non posso basta confrontare due valori contenuti nella monade IO. So che questo è appunto lo scopo di monadi - concatenamento risultati insieme e operazioni scartando quando una certa condizione è soddisfatta - ma c'è un modo semplice per "avvolgere" la monade IO in questo caso per interrompere l'esecuzione della catena ad una condizione di mia scelta, senza scrivere un'istanza di MonadPlus?

Posso solo "unlift" i valori da f, ai fini della TakeWhile?

Si tratta di una soluzione in cui si inseriscono funtori? Funtori non hanno "cliccato" con me ancora, ma una sorta di avere l'impressione che questo potrebbe essere una buona situazione per usarli.


Aggiornamento:

@sth ha la risposta più vicina a quello che voglio - in realtà, che è quasi esattamente quello che stavo per, ma mi piace ancora per vedere se c'è un soluzione standard che isn' t esplicitamente ricorsiva - questo è Haskell, dopo tutto! Ripensando a come ho formulato la mia domanda, ora mi rendo conto che non sono stato abbastanza chiaro circa il mio comportamento desiderato.

La funzione f ho usato sopra per un esempio è puramente esemplificativa. Le funzioni reali sono scritti in C e utilizzati esclusivamente per i loro effetti collaterali. Non posso usare @ il suggerimento di Tom di mapM_ f (takeWhile (<4) [0..5]) perché non ho idea se ogni ingresso sarà davvero tradurrà in successo o il fallimento fino eseguito.

Io in realtà non interessa il lista restituita, o - voglio solo chiamare le funzioni C fino a quando la lista è esaurita o la prima funzione C restituisce un codice di errore

.

In pseudocodice in stile C, il mio comportamento sarebbe:

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

Quindi, di nuovo, la risposta di @ qc esegue l'esatto comportamento che voglio, se non che i risultati possono (devono?) Essere eliminati. Una funzione dropWhileM_ sarebbe equivalente per i miei scopi. Perché non c'è una funzione così o takeWhileM_ in Control.Monad? Vedo che ci fosse una discussione simile su una mailing list , ma sembra che nulla è venuto di questo.

È stato utile?

Soluzione

Si potrebbe definire sequenza come

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

Il problema con liftM2 che hai visto è che non ha l'opportunità di fermare m2, che potrebbe essere 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)

guard come nel seguente sembra accattivante:

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)

Il codice di cui sopra avrà esito negativo nella vostra applicazione perché la monade IO non è un'istanza di MonadPlus .

Quindi, tenere la mano un po 'più

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

Anche se as è un elenco di azioni oltre 1 a 10, l'uscita è

1
2
3
4
[1,2,3]

scartando il risultato è quindi banale:

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

Si noti l'uso di [1..] che mostra il nuovo rel="noreferrer"> combinatore .


Si può preferire 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)

Si noti che differisce leggermente da arco dal fatto che comprende l'elemento in mancanza nella lista dei risultati. seconda della coppia sono le azioni rimanenti. Ad esempio:

*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]

Ancora un'altra alternativa:

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

Si noti che il senso del predicato è completato:

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

Altri suggerimenti

Non credo che ci sia qualcosa come un takeWhileM nella libreria standard, ma si potrebbe scrivere voi stessi in modo che solo la quantità di IO, se necessario viene eseguito:

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 []

L'elenco fornito è valutata solo fino a quando viene trovato un elemento, che non corrisponde il predicato:

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

Modifica:. Ora vedo quello che stai cercando per

gbacon postato una bella funzione sequenceWhile, che è quasi il "primitivo" è necessario.

In realtà, dal momento che siete interessati solo gli effetti collaterali, sequenceWhile_ dovrebbe essere sufficiente. Ecco una definizione (di nuovo, ispirato da gbacon, lui votare in su!):

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

Si chiama questo modo:

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

risposta originale:

Non si può semplicemente "unlift" i valori della IO Monade per l'utilizzo con takeWile, ma è possibile takeWhile "lift" per l'utilizzo all'interno di una Monade!

Il liftM funzione avrà una funzione (a -> b) a una funzione di (m a -> m b), dove m è una monade.

(Come nota a margine, è possibile trovare una funzione come questa con la ricerca per il suo tipo su Hoogle , in questo caso la ricerca di: Monad m => (a -> b) -> (m a -> m b) )

Con liftM si può fare questo:

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]

Ora, questo potrebbe non essere quello che si voleva. Il mapM applicherà la funzione f per l'intero elenco in sequenza, prima di ritornare una lista. Tale elenco risultante viene poi passato alla funzione takeWhile sollevato.

Se si desidera interrompere la stampa dopo il terzo elemento, dovrete smettere di chiamare la stampa. Ciò significa che, non si applicano f a tale elemento. Quindi, vi ritroverete con qualcosa di semplice come:

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

A proposito, si dovrebbe chiedersi perché mapM sarà prima di stampare tutto ciò, prima di tornare alla lista. Si può vedere questo sostituendo le funzioni con le loro definizioni:

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:[])

Questo processo di sostituzione di funzioni con le loro definizioni si chiama ragionamento equazionale .

Se non ho commesso errori, è ora possibile (si spera) vedere che mapM (usando sequence) prima di stampare tutto, e poi restituisce una lista.

È possibile utilizzare l'uno dall'altro "Lista" pacchetto .

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 un elenco contenente monadica 0..5 che non esegue alcuna operazione monadici
  • fmap f a che i risultati della lista in un ListT IO (IO Int) che svolge ancora nessuna azione monadici, contiene solo quelli.
  • joinM si trasforma, che in un ListT IO Int. ogni azione contenuta otterrebbe eseguito quando l'oggetto viene consumato e il suo risultato sarà il valore nella lista.
  • takeWhile è generalizzato per qualsiasi List. Sia [] e "Monad m => ListT m" sono esempi di List.
  • execute consuma la lista monadica, l'esecuzione di tutte le sue azioni.
  • Nel caso siate interessati ai risultati che è possibile utilizzare "toList :: List m => m a -> ItemM m [a]" ( "ItemM (ListT IO)" è IO). quindi in questo caso si tratta di "toList :: ListT IO a -> IO [a]". Meglio ancora è possibile continuare a utilizzare le funzioni di ordine superiore, come scanl, ecc per elaborare l'elenco monade in quanto è in esecuzione.

In tempi più recenti, è possibile utilizzare il MonadList hackage che include funzioni a portata di mano nofollow come takeWhileM, dropWhileM, deleteByM e molti di più.

Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top