Напишите в начале файла в Haskell

Я пытаюсь добавить число в начале файла, но функция "appendFile" добавляет в конец файла.

Я написал это, но это не сработало.

myAppendFile file = do x <- readFile file
                   writeFile file "1"
                   appendFile file x
                   return x

когда я это сделаю:

*main> myAppendFile "File.txt"

ошибка

the ressource is busy (file is locked)

так как я могу написать что-то в начале файла?

Ответ 1

Как отметил Габриэль, проблема заключается в том, что readFile считывает содержимое файла по требованию и закрывает основной дескриптор файла только тогда, когда содержимое файла полностью потребляется.

Решение состоит в том, чтобы потребовать полное содержимое файла, чтобы обработчик закрылся перед тем, как сделать writeFile.

Короткий ответ: используйте readFile из строгой версии.

import qualified System.IO.Strict as SIO
import Data.Functor

myAppendFile :: FilePath -> IO String
myAppendFile file = do
    x <- SIO.readFile file
    writeFile file "1"
    appendFile file x
    return x

main :: IO ()
main = void $ myAppendFile "data"

Другим простым "взломом" для этого является использование seq:

myAppendFile :: FilePath -> IO String
myAppendFile file = do
    x <- readFile file
    length x `seq` writeFile file "1"
    appendFile file x
    return x

seq a b в основном просто b плюс, что когда b требуется, a (length x в этом случае) оценивается как WHNF перед тем, как проверять b, это потому, что length требует, чтобы его аргумент (x в этом случае) проходил до тех пор, пока не будет показан пустой список [], поэтому требуется полное содержимое файла.

Имейте в виду, что с помощью строгого ввода-вывода ваша программа будет хранить все содержимое файла (в виде списка Char s) в памяти. Для игрушечных программ это прекрасно, но если вы заботитесь о производительности, вы можете взглянуть на bytestring или text в зависимости от независимо от того, связана ли ваша программа с байтами или текстами (если вы хотите иметь дело с текстом unicode). Они намного эффективнее, чем String.

Ответ 2

Мне удалось заставить его работать, но я использовал ByteString, который является строгим типом данных:

import Data.ByteString.Char8 as B

myAppendFile file = do
    x <- B.readFile file
    B.writeFile file $ B.pack "1"
    B.appendFile file x
    return $ B.unpack x

main = myAppendFile "c:\\test.txt"

Ваш код дал вам эту ошибку из-за Haskell лень. Когда вы пытались написать что-то в файле, данные не были полностью прочитаны в x, потому что haskell не нуждался в значении x до этой точки кода. Вот почему Haskell все еще висит файл.

Еще одно замечание, если у вас есть еще одно ленивое чтение перед этой функцией, замените его также на строгую версию, потому что вы снова столкнетесь с проблемами.

Ответ 3

Традиционный способ сделать это будет работать и в Haskell. Создайте новый временный файл, переместите байты из старого в временный, а затем переместите временный файл поверх старого. Вы можете/должны использовать ByteString или некоторые из них для эффективности, но вы можете выполнять копирование в кусках, вместо того, чтобы читать все в памяти, а затем снова выплюнуть. Я думаю, что pipes и conduit оба предлагают интерфейсы, которые должны сделать это более приятным.