2012-05-09 65 views
9

Piszę demona, który czyta coś z małego pliku, modyfikuje go i zapisuje z powrotem do tego samego pliku. Muszę się upewnić, że każdy plik zostanie zamknięty zaraz po przeczytaniu, zanim spróbuję go napisać. Muszę również upewnić się, że każdy plik jest zamykany natychmiast po napisaniu, ponieważ czasami mogę od razu go odczytać.Zapewnianie zamykania plików natychmiast

Przyjrzałem się używaniu binary-strict zamiast binarnego, ale wydaje się, że tylko zapewnia ścisłe Get, a nie ścisłe Put. Taki sam problem z System.IO.Strict. I czytając binarnie ścisłą dokumentację, nie jestem pewien, czy naprawdę rozwiązuje mój problem polegający na tym, że pliki są natychmiast zamykane. Jaki jest najlepszy sposób, aby sobie z tym poradzić? DeepSeq?

Oto bardzo uproszczony przykład, który daje pojęcie o strukturze mojej aplikacji. Ten przykład kończy się z oczywistych powodów, gdy znika oczywisty problem.

import Data.Binary (Binary, encode, decode) 
import Data.ByteString.Lazy as B (readFile, writeFile) 
import Codec.Compression.GZip (compress, decompress) 

encodeAndCompressFile :: Binary a => FilePath -> a -> IO() 
encodeAndCompressFile f = B.writeFile f . compress . encode 

decodeAndDecompressFile :: Binary a => FilePath -> IO a 
decodeAndDecompressFile f = return . decode . decompress =<< B.readFile f 

main = do 
    let i = 0 :: Int 
    encodeAndCompressFile "test.dat" i 
    doStuff 

doStuff = do 
    i <- decodeAndDecompressFile "test.dat" :: IO Int 
    print i 
    encodeAndCompressFile "test.dat" (i+1) 
    doStuff 

Odpowiedz

7

Należy rozważyć użycie pakietu, takiego jak conduit, pipes, lub enumerator. Zapewniają wiele korzyści leniwego IO (prostszy kod, potencjalnie mniejszy ślad pamięci) bez leniwego IO. Poniżej przedstawiono przykład użycia conduit i cereal:

import Data.Conduit 
import Data.Conduit.Binary (sinkFile, sourceFile) 
import Data.Conduit.Cereal (sinkGet, sourcePut) 
import Data.Conduit.Zlib (gzip, ungzip) 
import Data.Serialize (Serialize, get, put) 

encodeAndCompressFile :: Serialize a => FilePath -> a -> IO() 
encodeAndCompressFile f v = 
    runResourceT $ sourcePut (put v) $$ gzip =$ sinkFile f 

decodeAndDecompressFile :: Serialize a => FilePath -> IO a 
decodeAndDecompressFile f = do 
    val <- runResourceT $ sourceFile f $$ ungzip =$ sinkGet get 
    case val of 
    Right v -> return v 
    Left err -> fail err 

main = do 
    let i = 0 :: Int 
    encodeAndCompressFile "test.dat" i 
    doStuff 

doStuff = do 
    i <- decodeAndDecompressFile "test.dat" :: IO Int 
    print i 
    encodeAndCompressFile "test.dat" (i+1) 
    doStuff 
+0

Bezwstydna wtyczka: 'pipe' wkrótce wyjdzie z szybkim, deterministycznym i złożonym zarządzaniem zasobami w ciągu tygodnia. –

+0

@GabrielGonzalez Excellent. Pinguj mnie, kiedy zostanie wydany, a ja zaktualizuję tę odpowiedź. –

+1

Gotowe.Właśnie ogłosiłem to na [reddit] (http://www.reddit.com/r/haskell/comments/txkb0/pipes_20_pipe_finalization/). –

11

Wszystkie pliki "puts" lub "write" do plików są ścisłe. Akt z writeFile wymaga, aby wszystkie dane Haskella były oceniane w celu umieszczenia go na dysku.

Konieczne jest skoncentrowanie się na leniwym wprowadzeniu danych wejściowych. W powyższym przykładzie leniwie czytasz plik, a następnie leniwie go dekoduje.

Zamiast tego spróbuj dokładnie przeczytać plik (np. Ze ścisłymi bajtami), a wszystko będzie dobrze.

+0

Jestem zdezorientowany. Wyobrażam sobie, że 'i' jest początkowo związany z thunk w' doStuff', i że nie ma żadnego IO od czasu użycia leniwego 'readFile'. Jednakże, gdy "drukujemy", to nie wymusza to oceny "i" i zakończenia całej operacji wejścia/wyjścia? Czy 'decompress' nie odczytał całego pliku, więc jest otwarty? – pat

+0

Don, twoje wyjaśnienie pomogło mi zrozumieć, dlaczego nie potrzebuję ścisłych wersji "puts" lub "write"; Dziękuję za to. Jednak myślę, że musiałbym również znaleźć ścisłą wersję dekompresji. Ostatecznie poszedłem z rozwiązaniem Nathana, ponieważ uważam, że łatwiej jest podążać za kodem. – mhwombat

2

alternatywa do stosowania przewodów et al. byłoby po prostu użyć System.IO, co pozwoli ci kontrolować jawnie, gdy pliki są zamykane w odniesieniu do kolejności wykonania zamówienia.

Można użyć openBinaryFile następnie normalnych operacji odczytu (chyba te z Data.ByteString) i hClose kiedy skończysz z nim, albo withBinaryFile, który zamyka plik automatycznie (ale uwaga this sort of problem).

Bez względu na metodę, której używałeś, jak powiedział Don, prawdopodobnie chcesz przeczytać jako surową analizę, a następnie przekonwertować ją do leniwej później z fromChunks.

Powiązane problemy