2013-11-01 7 views
6

Questa è una domanda noob.Haskell: nascondere i guasti nell'IO pigro

mi piacerebbe scrivere una funzione che fornisce un flusso pigro di immagini, presumibilmente qualcosa di simile:

imageStream :: [IO Image] 

Purtroppo, la funzione che legge le immagini possono fallire, così sembra:

readImage :: IO (Maybe Image) 

Quindi, la funzione I posso scrittura assomiglia:

maybeImageStream :: [IO (Maybe Image)] 

Come si implementa una funzione come la seguente, pur mantenendo l'IO pigro?

flattenImageStream :: [IO (Maybe Image)] -> [IO Image] 

vista semantico, quando si chiede flattenImageStream per l'immagine successiva, che dovrebbe scorrere la lista e tentare di leggere ogni immagine. Lo fa finché non trova un'immagine che carica e la restituisce.

EDIT: Sembra esserci un disaccordo nelle risposte. Alcuni hanno suggerito soluzioni che usano sequence, ma sono abbastanza sicuro che l'ho provato e ho scoperto che distrugge la pigrizia. (Lo testerò di nuovo per essere sicuro quando torno al mio computer.) Qualcuno ha anche suggerito di usare unsafeInterleaveIO. Dalla documentazione per quella funzione, sembra che avrebbe funzionato, ma ovviamente voglio rispettare il sistema di tipi il più possibile.

+0

Sei sicuro di voler un '[Immagine IO]'? 'IO [Image]' non sarebbe più facile da lavorare? – jwodder

+0

@jwodder, potrei sbagliarmi, ma penso che 'IO [Immagine]' implica che le immagini verranno caricate rigorosamente, mentre voglio caricarle pigramente a causa della memoria e di altre cose. – emchristiansen

+0

Nah saranno ancora pigri. se restituisci [Immagine IO] non hai caricato nulla. Hai solo un elenco di funzioni io da eseguire per caricare le immagini. – DiegoNolan

risposta

9

È possibile utilizzare ListT da pipes, che fornisce un'alternativa più sicura a pigro IO che fa la cosa giusta in questo caso.

Il modo in cui si modella il vostro flusso pigro di immagini potenzialmente fallimento è:

imageStream :: ListT IO (Maybe Image) 

Supponendo che hai avuto una qualche funzione di caricamento delle immagini di tipo:

loadImage :: FileName -> IO (Maybe Image) 

.. poi il modo in cui si costruisce come un flusso sarebbe qualcosa di simile:

imageStream = do 
    fileName <- Select $ each ["file1.jpg", "file2.jpg", "file3.jpg"] 
    lift $ loadImage fileName 

Se si utilizza il dirstream library, allora puoi anche fluire pigramente sul contenuto della directory.

La funzione che filtra solo i risultati di successo avrebbe questo tipo:

flattenImageStream :: (Monad m) => ListT m (Maybe a) -> ListT m a 
flattenImageStream stream = do 
    ma <- stream 
    case ma of 
     Just a -> return a 
     Nothing -> mzero 

Si noti che questa funzione è disponibile per qualsiasi monade base, m. Non c'è niente IO -specifico a riguardo. Conserva anche la pigrizia!

Applicando flattenImage-imageStream, ci dà qualcosa di tipo:

finalStream :: List IO Image 
finalStream = flattenImage imageStream 

Ora diciamo che avete qualche funzione che consuma queste immagini, di tipo:

useImage :: Image -> IO() 

Se si desidera elaborare il ListT finale utilizzando la funzione useImage, è sufficiente scrivere:

main = runEffect $ 
    for (every finalStream) $ \image -> do 
     lift $ useImage image 

Questo consumerà pigramente il flusso di immagini.

Naturalmente, si potrebbe anche giocare a golf codice e combinare tutto questo nella seguente versione molto più breve:

main = runEffect $ for (every image) (lift . useImage) 
    where 
    image = do 
     fileName <- Select $ each ["file1.jpg", "file2.jpg", "file3.jpg"] 
     maybeImage <- lift $ loadImage fileName   
     case maybeImage of 
      Just img -> return img 
      Nothing -> mzero 

Sto anche pensando di aggiungere una definizione fail per ListT in modo che si può solo scrivere :

main = runEffect $ for (every image) (lift . useImage) 
    where 
    image = do 
     fileName <- Select $ each ["file1.jpg", "file2.jpg", "file3.jpg"] 
     Just img <- lift $ loadImage fileName   
     return img 
+0

Funziona alla grande, ma dopo aver letto di più su 'pipes' ho deciso di usare' Producer'/'Consumer' /' spawn' per la mia soluzione completa, che ha bisogno di 'pipe-concurrency'. È una biblioteca impressionante; grazie :) – emchristiansen

+0

Prego! –

0

L'implementazione di questa come richiesto sembra che sarebbe necessario conoscere al di fuori della monade IO se un valore all'interno IO era Nothing, e come IO è stato progettato per evitare che i suoi valori da "fuoriuscita" nel mondo esterno puramente funzionale (unsafePerformIO nonostante), questo sarebbe impossibile. Invece, vi consiglio di produrre un IO [Image]: utilizzare sequence per convertire il [IO (Maybe Image)] a IO [Maybe Image], e quindi utilizzare Data.Maybe.catMaybes all'interno della monade IO (ad esempio, con fmap o liftM) per convertire in IO [Image], ad esempio:

flattenImageStream = fmap catMaybes $ sequence maybeImageStream 
+0

Sono sicuro che ho provato questo, e ho ottenuto un elenco non pigro di 'Immagine'. Potrei dire che è non-pigro perché registro ogni volta che viene caricata un'immagine. – emchristiansen

1

come u suggerito può trasformare [ma] in m [a] impiegando sequenza

in modo da ottenere:

imageStream :: IO [Image] 

quindi è possibile utilizzare ca yMaybes da Data.Maybe di tenere solo i valori soli:

catMaybes `liftM` imageStream 
0

non credo che qualsiasi di queste altre risposte stanno facendo esattamente quello che vuoi. Perché sono abbastanza sicuro che l'catMaybes salterà l'immagine e non proverai a ricaricarla. Se vuoi continuare a provare a ricaricare un'immagine, prova questo.

flattenImageStream :: [IO (Maybe Image)] -> IO [Image] 
flattenImageStream xs = mapM untilSuc xs 

untilSuc :: IO (Maybe a) -> IO a 
untilSuc f = do 
    res <- f 
    case res of 
     Nothing -> untilSuc f 
     Just i -> return i 

Ma quello che stai facendo è un po 'strano. Cosa succede se si ha il percorso del file sbagliato? Cosa succede se l'immagine non può essere semplicemente caricata? Proverai solo a caricare un'immagine per sempre. Probabilmente dovresti avere un numero di volte per provare a caricare l'immagine prima che si arrenda.

+0

Questo non è quello che sto cercando di fare. Voglio saltare in modo trasparente le immagini che non si caricano, non continuare a provare per sempre a caricarle. – emchristiansen

+0

"dovrebbe continuare a tentare di leggere le immagini fino a quando non riesce e restituire quell'immagine." Ok, ma non era molto esplicito nella tua domanda. Usa le loro risposte allora. – DiegoNolan

+0

Il mio male; Modificherò la domanda. Grazie per averlo indicato. – emchristiansen