Using Temporary Files
In the last article we learned about seeking. Today we'll see another context where we can use these tools while learning about another new idea: temporary files.
Our "new" function for today is openTempFile
. Its type signature looks like this:
openTempFile :: FilePath -> String -> IO (FilePath, Handle)
The first argument is the directory in which to create the file. The second is a "template" for the file name. The template can look like a normal file name, like name.extension
. The name of the file that will actually be created will have some random digits appended to the name. For example, we might get name1207-5.extension
.
The result of the function is that Haskell will create the file and pass a handle to us in ReadWrite
mode. So our two outputs are the full path to the file and its handle.
Despite the name openTempFile
, this function won't do anything to delete the file when it's done. You'll still have to do that yourself. However, it does have some useful built-in mechanics. It is guaranteed to not overwrite an existing file on the system, and it also gives limited file permissions so it can't be used by an attacker.
How might we use such a file? Well let's suppose we have some calculation that we break into multiple stages, so that it uses an intermediate file in between. As a contrived example, let's suppose we have two functions. One that writes fibonacci numbers to a file, and another that takes the sum of numbers in a file. We'll have both of these operate on a pre-existing Handle
object:
writeFib :: Integer -> Handle -> IO ()
writeFib n handle = writeNum (0, 1) 0
where
writeNum :: (Integer, Integer) -> Integer -> IO ()
writeNum (a, b) x = if x > n then return ()
else hPutStrLn handle (show a) >> writeNum (b, a + b) (x + 1)
sumNumbers :: Handle -> IO Integer
sumNumbers handle = do
hSeek handle AbsoluteSeek 0
nums <- (fmap read . lines) <$> hGetContents handle
return $ sum nums
Notice how we "seek" to the beginner of the file in our reading function. This means we can use the same handle for both operations, assuming the handle has ReadWrite
mode. So let's see how we put this together with openTempFile
:
main :: IO ()
main = do
n <- read <$> getLine
(file, handle) <- openTempFile "/tmp/fib" "calculations.txt"
writeFib n handle
sum <- sumNumbers handle
print sum
hClose handle
removeFile file
A couple notes here. First, if the directory passed to openTempFile
doesn't exist, this will cause an error. We also need to print the sum before closing the handle, or else Haskell will not actually try to read anything until after closure due to laziness!
But aside from these caveats, our function works! If we don't remove
the file, then we'll be able to see the file at a location like /tmp/fib/calculations6132-6.txt
.
This example doesn't necessarily demonstrate why we would use openTempFile
instead of just giving the file the name calculations.txt
. The answer to that is our process is now safer with respect to concurrency. We could run this same operation on different threads in parallel, and there would be no file conflicts. We'll see exactly how to do that later this year!
For now, make sure you're subscribed to our monthly newsletter so that you can stay up to date with all latest information and offers! If you're already subscribed, take a look at our subscriber resources that can help you improve your Haskell!