How to delete something from a text file in Haskell

Viewed 121

I have been trying to remove a user from a text file in Haskell with no success. I know how to read from the file, as well as writing to it. We have a function that is called findUser that simply finds the user in the text file and returns a list with the user's password, username and account balance. The username is unique. When we want to add balance to the user's account, we need to get the user's old balance and add that to the asked amount by the user. That has already been done. But the problem is that now that we have the new user with the updated balance, we append that to the text file. But then we have to delete the old user with the old balance. That is where we are stuck at, at the moment. All help would be appreciated. Below is the code that we are trying to get to delete the old user.

-- = Comment
-- oldUserBalance -> addBalance -> IO ()
addNewUserBalanceToFile :: [String] -> Int -> IO ()
addNewUserBalanceToFile (userName:userPassword:userBalance:) addBalance = do
   let 
      updatedBalance = [userName,userPassword,show(read userBalance + addBalance)]
      appendNewUser = unwords updatedBalance
      fileName = "users.txt"
   handle <- openFile fileName ReadMode -- Opens the file so that we can read from it
   contents <- hGetContents handle -- Store everything in the file users.txt 
   let 
      users = lines contents
      testDeleteUser = delete (unwords $ findUser userName $ words contents) $ words contents
   print testDeleteUser
   putStrLn "These are the users"
   putStr $ unlines users
   appendFile fileName $ appendNewUser ++ "\n"
   hClose handle 
addNewUserBalanceToFile  _ _ = return ()
1 Answers

The most pragmatic thing is to just write the file completely from scratch. Don't bother with handles and appending and all, just read an entire file and write a fresh one with the modifications. This is much easier and more flexible for data formats where appending just doesn't make sense (e.g. JSON key-value maps).

Note that the string versions of Haskell's IO functions are lazy, which regularly leads to strange behaviour (the new file is written whilst the old one is still being read) and is now widely considered a design mistake. In modern Haskell, one would normally use either the Text or ByteString versions, which avoid this problem; an alternative is to use DeepSeq to force strict reading.

import Control.DeepSeq (deepseq)

addNewUserBalanceToFile (userName:userPassword:userBalance:) addBalance = do
   let updatedBalance, appendNewUser, fileName = ...
   contents <- readFile fileName
   
   let users = lines contents
       updatedContents = ... ++ appendNewUser ++ "\n"

   updatedContents `deepseq` writeFile fileName updatedContents

Now, you might well remark that writing everything from scratch is not efficient if the file was big. It can also lead to concurrency issues if multiple programs access the file. You'd be right.

However, if these concerns matter to you, you shouldn't be using manual file creation at all (certainly not in plaintext form), but instead use a database that has been carefully designed and debugged.

Related