2009-04-16 16 views
10

Considerare il seguente programma Haskell. Sto provando a programmare in uno "stile di flusso" in cui le funzioni operano su flussi (implementati qui semplicemente come elenchi). Cose come NormalStreamFunc funzionano alla grande con liste pigre. Posso passare una lista infinita a NormalStreamFunc ed estrarre efficacemente un'altra lista infinita, ma con una funzione mappata su ciascun valore. Cose come effectfulStreamFunc non funzionano così bene. L'azione IO significa che ho bisogno di valutare l'intera lista prima di poter estrarre i singoli valori. Ad esempio, l'output del programma è questo:Stream Haskell con effetti IO

a 
b 
c 
d 
"[\"a\",\"b\"]" 

ma quello che voglio è un modo per scrivere effectfulStreamFunc modo che il programma produce questo:

a 
b 
"[\"a\",\"b\"]" 

lasciando le azioni rimanenti non valutata. Posso immaginare una soluzione usando unsafePerformIO, ma diciamo che sto prendendo quello fuori dal tavolo. Ecco il programma:

import IO 

normalStreamFunc :: [String] -> [String] 
normalStreamFunc (x:xs) = reverse(x) : normalStreamFunc xs 

effectfulStreamFunc :: [String] -> IO [String] 
effectfulStreamFunc [] = return [] 
effectfulStreamFunc (x:xs) = do 
    putStrLn x 
    rest <- effectfulStreamFunc xs 
    return (reverse(x):rest) 

main :: IO() 
main = do 
    let fns = ["a", "b", "c", "d"] 
    es <- effectfulStreamFunc fns 
    print $ show $ take 2 es 

Aggiornamento:

Grazie a tutti per il feedback disponibile e premuroso. Non avevo mai visto l'operatore sequence, che è utile sapere. Avevo pensato ad un modo (meno elegante) di passare attorno ai valori IO (String) invece che alle stringhe, ma per lo stile di programmazione che è di utilità limitata, dal momento che voglio che altre funzioni di streaming agiscano sulle stringhe stesse, non su azioni che possono produrre una stringa. Ma, basandomi sul pensare attraverso le altre risposte, penso di capire perché questo sia irrisolvibile in generale. Nel caso semplice che ho presentato, quello che volevo veramente era l'operatore sequence, poiché stavo pensando che l'ordinamento dei flussi implicasse un ordinamento sulle azioni. In realtà, nessun ordine di questo tipo è necessariamente implicito. Ciò diventa più chiaro per me quando penso a una funzione di flusso che richiede due flussi come input (ad esempio, l'aggiunta a coppie di due flussi). Se entrambi i flussi "in entrata" eseguono IO, l'ordinamento di quelle azioni IO non è definito (a meno che, ovviamente, non lo definiamo sequenziandolo noi stessi nella monade IO). Problema risolto, grazie a tutti!

risposta

7

Che ne dite di questo codice:

import IO 

normalStreamFunc :: [String] -> [String] 
normalStreamFunc (x:xs) = reverse(x) : normalStreamFunc xs 

effectfulStreamFunc :: [String] -> [IO (String)] 
effectfulStreamFunc [] = [] 
effectfulStreamFunc (x:xs) = 
    let rest = effectfulStreamFunc xs in 
     (putStrLn x >> return x) : rest 

main :: IO() 
main = do 
    let fns = ["a", "b", "c", "d"] 
    let appliedFns = effectfulStreamFunc fns 
    pieces <- sequence $ take 2 appliedFns 
    print $ show $ pieces 

Piuttosto che effectfulStreamFunc effettivamente facendo qualsiasi IO, questa invece crea un elenco di azioni da eseguire IO. (Si noti la modifica del tipo di firma.) La funzione principale poi prende 2 di queste azioni, li esegue e stampa i risultati:

a 
b 
"[\"a\",\"b\"]" 

Questo funziona perché il tipo IO (String) è solo una funzione/valore come qualsiasi altro che si può inserire una lista, passarci intorno, ecc. Si noti che la sintassi do non si verifica in "effectfulStreamFunc" - in realtà è una pura funzione, nonostante l'"IO" nella sua firma. Solo quando eseguiamo sequence su quelli principali, gli effetti si verificano effettivamente.

+1

Se ti piace ancora la notazione fare, si potrebbe scrivere la seconda clausola di effectfulStreamFunc come: effectfulStreamFunc (x: xs) = let putAction = do putStrLn x restituisci x in putAction: effectfulStreamFunc xs Penso che legga un po 'meglio. –

+0

Buon punto. Suppongo che la sintassi di do sia ortogonale al problema di eseguire effettivamente la monade. –

2

Non capisco il tuo obiettivo principale ma l'utilizzo di putStrLn comporta la valutazione dell'intero elenco perché valuterà l'argomento quando viene eseguito. Considerare

import IO 

normalStreamFunc :: [String] -> [String] 
normalStreamFunc (x:xs) = reverse(x) : normalStreamFunc xs 

effectfulStreamFunc :: [String] -> IO [String] 
effectfulStreamFunc [] = return [] 
effectfulStreamFunc (x:xs) = do 
    rest <- effectfulStreamFunc xs 
    return (reverse(x):rest) 

main :: IO() 
main = do 
    let fns = ["a", "b", undefined,"c", "d"] 
    es <- effectfulStreamFunc fns 
    print $ show $ take 2 es 

questo si traduce in "[\" a \ "\ "B \"]", mentre si utilizza la versione putStrLn si traduce in un'eccezione.

1

Come accennato da Tomh, non si può davvero fare questo "in sicurezza", perché si sta infrangendo la trasparenza referenziale in Haskell.Stai cercando di eseguire effetti collaterali pigramente, ma la cosa importante è che non ti è garantito in quale ordine o se le cose vengono valutate, quindi in Haskell, quando gli dici di eseguire un effetto collaterale, viene sempre eseguito, e nell'ordine esatto specificato. (vale a dire in questo caso gli effetti collaterali dalla chiamata ricorsiva di effectfulStreamFunc prima dello return, perché quello era l'ordine in cui erano elencati) Non è possibile farlo pigramente senza usare rischi.

Si può provare a utilizzare qualcosa come unsafeInterleaveIO, che è come IO pigro (ad esempio hGetContents) è implementato in Haskell, ma ha i suoi problemi; e hai detto che non vuoi usare cose "non sicure".

import System.IO.Unsafe (unsafeInterleaveIO) 

effectfulStreamFunc :: [String] -> IO [String] 
effectfulStreamFunc [] = return [] 
effectfulStreamFunc (x:xs) = unsafeInterleaveIO $ do 
    putStrLn x 
    rest <- effectfulStreamFunc xs 
    return (reverse x : rest) 

main :: IO() 
main = do 
    let fns = ["a", "b", "c", "d"] 
    es <- effectfulStreamFunc fns 
    print $ show $ take 2 es 

In questo caso l'output è simile al seguente

"a 
[\"a\"b 
,\"b\"]"