Text, Files, and Directories
Prerequisites: IO and do-notation
Reading and writing files
main :: IO ()
main = do
writeFile "notes.txt" "first line\nsecond line\n"
appendFile "notes.txt" "third line\n"
contents <- readFile "notes.txt"
let ls = lines contents
print (length ls)
mapM_ (\(n, l) -> putStrLn (show n ++ ": " ++ l)) (zip [1 :: Int ..] ls)
3
1: first line
2: second line
3: third line
readFile reads lazily: the file is read as the string is consumed.
That's convenient for big files, but it means you shouldn't write to a
file while a lazy read of it is still in progress.
Data.Text
String is a linked list of characters — fine for learning, slow for
real text. The text package (shipped with GHC) provides Text, a
compact, fast Unicode string with a rich API:
{-# LANGUAGE OverloadedStrings #-}
import qualified Data.Text as T
import qualified Data.Text.IO as TIO
main :: IO ()
main = do
let t = " Hello, Haskell World " :: T.Text
TIO.putStrLn (T.strip t)
TIO.putStrLn (T.toUpper (T.strip t))
print (T.words "one two three")
print (T.splitOn "," "a,b,,c")
print (T.replace "World" "Text" t)
print (T.length t, T.isInfixOf "Haskell" t)
TIO.putStrLn (T.intercalate " | " ["x", "y", "z"])
putStrLn (T.unpack (T.take 5 (T.strip t)))
Hello, Haskell World
HELLO, HASKELL WORLD
["one","two","three"]
["a","b","","c"]
" Hello, Haskell Text "
(24,True)
x | y | z
Hello
OverloadedStringslets string literals beText(or any type with anIsStringinstance), so you don't have to callT.packeverywhere.T.pack/T.unpackconvert betweenStringandText.Data.Text.IOreads and writes files strictly (TIO.readFile).- For binary data, use
Data.ByteStringfrom thebytestringpackage.
Files safely with handles
import System.IO
main :: IO ()
main = do
withFile "log.txt" WriteMode $ \h -> do
hPutStrLn h "started"
hPutStrLn h "finished"
withFile "log.txt" ReadMode $ \h -> do
first <- hGetLine h
eof <- hIsEOF h
putStrLn (first ++ ", more lines: " ++ show (not eof))
started, more lines: True
withFile opens a handle, runs your code, and closes the handle even if
an exception is thrown. The standard handles are stdin, stdout, and
stderr — hPutStrLn stderr "message" writes an error message.
Directories
import System.Directory
import Data.List (sort)
main :: IO ()
main = do
createDirectoryIfMissing True "data/archive"
writeFile "data/a.txt" "A"
writeFile "data/b.txt" "B"
files <- listDirectory "data"
print (sort files)
doesFileExist "data/a.txt" >>= print
renameFile "data/b.txt" "data/archive/b.txt"
listDirectory "data/archive" >>= print
removeDirectoryRecursive "data"
doesDirectoryExist "data" >>= print
["a.txt","archive","b.txt"]
True
["b.txt"]
False
System.Directory comes from the directory package, and
System.FilePath (</>, takeExtension, ...) from filepath — both ship
with GHC.