2010-05-06 10 views
10

Sto cercando di ottenere una comprensione più profonda della pigrizia in Haskell.In che modo la pigrizia e l'I/O lavorano insieme in Haskell?

stavo immaginando il seguente frammento di oggi:

data Image = Image { name :: String, pixels :: String } 

image :: String -> IO Image 
image path = Image path <$> readFile path 

L'appello è che si potrebbe semplicemente creare un'istanza immagine e passarlo in giro; se ho bisogno dei dati di immagine sarebbe letto pigramente - in caso contrario, il costo di tempo e la memoria di leggere il file potrebbe essere evitato:

main = do 
    image <- image "file" 
    putStrLn $ length $ pixels image 

Ma è così che funziona in realtà? In che modo la pigrizia è compatibile con IO? Sarà letto il file di lettura indipendentemente dal fatto che acceda allo pixels image o il runtime lascerà quel thunk non valutato se non mi riferisco mai ad esso?

Se l'immagine viene effettivamente letta pigramente, non è possibile che le azioni di I/O vengano eseguite in modo anomalo? Ad esempio, cosa succede se immediatamente dopo aver chiamato image ho eliminato il file? Ora la chiamata putStrLn non troverà nulla quando tenta di leggere.

risposta

17

In che modo la pigrizia è compatibile con I/O?

Risposta breve: Non lo è.


Risposta lunga: IO azioni sono strettamente in sequenza, per più o meno le ragioni che stai pensando. Ovviamente qualsiasi calcolo puro fatto con i risultati può essere pigro; ad esempio se leggi un file, esegui qualche elaborazione e poi stampi alcuni risultati, è probabile che qualsiasi elaborazione non necessaria per l'output non verrà valutata. Tuttavia, verrà letto l'intero file, anche le parti che non si usano mai. Se vuoi ti pigro/O, si hanno circa due opzioni:

  • rotolare il proprio esplicito routine lazy-carico e quali, come si farebbe in qualsiasi lingua rigorosa. Sembra fastidioso, scontato, ma d'altra parte Haskell ha un linguaggio rigoroso e imperativo. Se vuoi provare qualcosa di nuovo e interessante, prova a guardare Iteratees.

  • Cheat come un imbroglione truffatore. Le funzioni such as hGetContents eseguiranno l'I/O pigro su richiesta, senza fare domande. Qual è il trucco? (Tecnicamente) infrange la trasparenza referenziale. Il codice puro può indirettamente causare effetti collaterali e possono accadere cose divertenti che comportano l'ordinamento di effetti collaterali se il tuo codice è veramente contorto. hGetContents e gli amici sono implementati using unsafeInterleaveIO, che è ... esattamente quello che dice sulla latta. Non è neanche lontanamente probabile che ti faccia saltare in faccia come usando unsafePerformIO, ma ti consideri avvisato.

+1

Grazie per questa risposta! In effetti, è stata la descrizione di HGetContents di RWH a confondermi su questo problema. Non mi ero reso conto che si trattava di un caso speciale e ho utilizzato sotto chiamate IO non sicure. Quindi, in pratica, il mio esempio legge il file non appena viene elaborata l'azione readFile? Sembra molto più coerente, se è così. – Bill

+5

@Bill: Ecco l'implementazione per readFile, direttamente dalle librerie standard di GHC: 'readFile name = openFile name ReadMode >> = hGetContents' Quindi no, il tuo esempio rientra nella categoria" cheating cheater ". Detto questo, le funzioni di I/O pigro di solito sono abbastanza sicure per la maggior parte degli usi pratici quotidiani, quindi non si suda troppo a meno che la purezza non sia molto importante per te. –

+1

So che Oleg dice che "UnsafeInterleaveIO" rompe la trasparenza referenziale, ma non sono d'accordo. Direi che è semplicemente non deterministico, come molte cose nella monade 'IO'. 'GetCurrentTime' interrompe la trasparenza referenziale perché posso usarlo per determinare quale delle due funzioni estrinsicamente uguali è implementata in modo più efficiente? –

9

L'I/O pigro interrompe la purezza di Haskell. I risultati da readFile vengono effettivamente prodotti pigramente, su richiesta. L'ordine in cui si verificano le operazioni di I/O non è fisso, quindi sì, potrebbero verificarsi "fuori servizio". Il problema dell'eliminazione del file prima di estrarre i pixel è reale. In breve, I/O pigro è una grande comodità, ma è uno strumento con bordi molto nitidi.

Il libro su Real World Haskell ha uno lengthy treatment of lazy I/O e ripercorre alcune delle insidie.

Problemi correlati