Take a look at this example:
#! cabal
{- cabal:
build-depends : base, text
default-extensions: BlockArguments GHC2021 UnicodeSyntax
-}
import Data.Text.Lazy.IO qualified as UnicodeStream
main = do
let fileName = "example.txt"
fileContents ← UnicodeStream.readFile fileName
UnicodeStream.writeFile fileName fileContents
It will error out like so:
% ./script.hs
cabal-script-script.hs: example.txt: withFile: resource busy (file is locked)
What is the standard way of removing such errors from my programs?
As I understand, this error happens exactly because I am trying to write to a file while it is still being lazily read. I have been hearing people say «do not use lazy IO, lazy IO is bad». I guess this is an example of lazy IO. How could I tell that this particular function readFile
is «lazy IO»? Why does it exist in standard libraries? Could it not have had some helpful type annotations?
I can imagine that this error is hard to prevent in general while retaining lazy IO. For example, say I have two hard linked files:
main = do
fileContents ← UnicodeStream.readFile "example.txt"
UnicodeStream.writeFile "example-hardlink.txt" fileContents
% ln example.txt example-hardlink.txt
% ./script.hs
cabal-script-script.hs: example-hardlink.txt: withFile: resource busy (file is locked)
Tragic. My program has failed without a fault of my own.
However, we could in principle have found out that these two are the same file, by looking more closely at the file system. Then, it seems like we could automatically lock the files being lazily read in such a way that an attempt to write forces the lazy IO all the way, thus avoiding the error.
In my ideal world, there is indeed such a fancy locking system and moreover all standard libraries work this way. Second best would be to have clear documentation that explains this issue — but I am sure I have never seen any specific study of it. Yet this seems to me an urgent issue.