2010-03-26 8 views
15

Ancora abbastanza nuovo per Haskell ..In Haskell, voglio leggere un file e poi scriverci sopra. Ho bisogno di annotazioni sulla severità?

Voglio leggere il contenuto di un file, fare qualcosa con esso possibilmente coinvolgendo IO (usando putStrLn per ora) e quindi scrivere nuovi contenuti nello stesso file.

mi si avvicinò con:

doit :: String -> IO() 
doit file = do 
    contents <- withFile tagfile ReadMode $ \h -> hGetContents h 
    putStrLn contents 
    withFile tagfile WriteMode $ \h -> hPutStrLn h "new content" 

Tuttavia questo non funziona a causa di pigrizia. Il contenuto del file non viene stampato. Ho trovato this post che lo spiega bene.

la soluzione proposta non è quella di includere all'interno del putStrLnwithFile:

doit :: String -> IO() 
doit file = do 
    withFile tagfile ReadMode $ \h -> do 
     contents <- hGetContents h 
     putStrLn contents 
    withFile tagfile WriteMode $ \h -> hPutStrLn h "new content" 

Questo funziona, ma non è quello che voglio fare. L'operazione in I sostituirà eventualmente putStrLn potrebbe essere lunga, non voglio mantenere il file aperto tutto il tempo. In generale, voglio solo essere in grado di estrarre il contenuto del file e poi chiuderlo prima di lavorare con quel contenuto.

La soluzione mi è venuta è la seguente:

doit :: String -> IO() 
doit file = do 
    c <- newIORef "" 
    withFile tagfile ReadMode $ \h -> do 
     a <- hGetContents h 
     writeIORef c $! a 
    d <- readIORef c 
    putStrLn d 
    withFile tagfile WriteMode $ \h -> hPutStrLn h "Test" 

Tuttavia, ritengo che questo lungo e un po 'offuscato. Non penso che avrei bisogno di un IORef solo per ottenere un valore, ma avevo bisogno di "posizionare" per mettere il contenuto del file. Inoltre, non funzionava ancora senza l'annotazione di rigore $! per writeIORef. Immagino che gli IORef non siano rigorosi per natura?

Qualcuno può consigliare un modo migliore e più breve per farlo mantenendo la semantica desiderata?

Grazie!

+3

Se pubblichi le dichiarazioni "import" necessarie per compilare il codice, altre persone potrebbero aiutarlo a eseguire il debug ... –

+2

Ottimizzazione prematura, radici malvagie, ecc. Perché hai paura di mantenere il descrittore di file per tutto il tempo necessario vero? – jrockway

+1

Lo IORef non ha l'effetto che percepisci. Un valore in uno IORef può essere altrettanto pigro di uno appena tornato da un blocco. Il tuo codice è equivalente a quello non funzionante: doit file = do { d <- conFile tagfile ReadMode $ \ h -> do { a <- hGetContents h; ritorno $! a }; ... Lo IORef è solo un cerchio senza senso da attraversare. Tuttavia, la battuta finale è che il seq regolare non è sufficiente per forzare un'intera stringa. Hai bisogno di un seq profondo. – luqui

risposta

21

Il motivo per cui il primo programma non funziona è che withFile chiude il file dopo l'esecuzione dell'azione IO inoltrata. Nel tuo caso, l'azione IO è hGetContents che fa non legge il file subito, ma solo come il suo contenuto è richiesto. Nel momento in cui si tenta di stampare il contenuto del file, withFile ha già chiuso il file, quindi la lettura non riesce (automaticamente).

È possibile risolvere questo problema da non reinventare la ruota e semplicemente utilizzando readFile e writeFile:

doit file = do 
    contents <- readFile file 
    putStrLn contents 
    writeFile file "new content" 

Ma supponiamo che si desidera che il nuovo contenuto dipendere il vecchio contenuto. Quindi non si può, in genere, è sufficiente fare

doit file = do 
    contents <- readFile file 
    writeFile file $ process contents 

perché il writeFile possono influenzare ciò che i rendimenti readFile (ricordate, non ha effettivamente letto il file ancora). In alternativa, a seconda del sistema operativo in uso, potrebbe non essere possibile aprire lo stesso file per la lettura e la scrittura su due manici separati.La soluzione semplice ma brutto è

doit file = do 
    contents <- readFile file 
    length contents `seq` (writeFile file $ process contents) 

che costringerà readFile per leggere l'intero file e chiuderlo prima che l'azione writeFile può iniziare.

+0

Dopo aver letto il post a cui ti colleghi, immagino che tu sappia già qualcosa di questo. Ma dal momento che c'è più di un problema di pigrizia qui ho pensato che fosse meglio approfondire. –

+0

Piuttosto che usare 'lunghezza contenuto 'seq' ...'. Penso che potresti usare l'estensione BangPatterns e riscrivere la riga precedente come "! Contents <- readFile file". – Alasdair

+1

Equivale a 'contenuto 'seq' ...', che non è sufficiente: valuterà solo il costruttore di contenuti di livello superiore (cioè se è vuoto), il che significa che verrà letto solo il primo blocco del file . –

0

È brutto ma è possibile forzare la lettura del contenuto chiedendo il length dell'input e seq 'it con la successiva istruzione nel proprio blocco. Ma in realtà la soluzione è utilizzare una versione rigorosa di hGetContents. Non sono sicuro di come si chiama.

9

Penso che il modo più semplice per risolvere questo problema è useing rigorosa IO:

import qualified System.IO.Strict as S 
main = do 
    file <- S.readFile "filename" 
    writeFile "filename" file 
1

è possibile duplicare il file Maniglia, scriva pigro con quello originale (fino alla fine del file) e di lettura pigra con un altro . Quindi nessuna annotazione di rigore coinvolta in caso di aggiunta al file.

import System.IO 
import GHC.IO.Handle 

main :: IO() 
main = do 
    h <- openFile "filename" ReadWriteMode 
    h2 <- hDuplicate h 

    hSeek h2 AbsoluteSeek 0 
    originalFileContents <- hGetContents h2 
    putStrLn originalFileContents 

    hSeek h SeekFromEnd 0 
    hPutStrLn h $ concatMap ("{new_contents}" ++) (lines originalFileContents) 

    hClose h2 
    hClose h 

La funzione hDuplicate è fornito dal modulo GHC.IO.Handle.

Restituisce un duplicato dell'impugnatura originale, con il proprio buffer. I due manici condivideranno comunque un puntatore di file. Il buffer della maniglia originale viene svuotato, incluso lo scarto di qualsiasi dato di input, prima che l'handle venga duplicato.

Con hSeek è possibile impostare la posizione della maniglia prima di leggere o scrivere.

Ma non sono sicuro di quanto sarebbe affidabile utilizzare "AbsoluteSeek 0" anziché "SeekFromEnd 0" per la scrittura, ovvero sovrascrivere i contenuti. Generalmente suggerisco di scrivere prima su un file temporaneo, ad esempio utilizzando openTempFile (da System.IO), e quindi sostituire l'originale.

Problemi correlati