2

I am still a beginner in Haskell, so after reading some writefile tutorials online, I see most of the writefile examples online are used inside the main function (main = IO ())

I am wondering whether it's possible to write a function that writes the results into a file using writefile when it is computed? In some programs (especially games), users might want to stop at a particular points of the game by saving the contents into a .txt file.

For example something like this: (this function does not work, just wondering how to make it work)

concat :: FilePath -> [[a]] -> [a]
concat txt [] = []`
concat txt (xs : xss) = do
y <- xs ++ concat xss
writeFile txt (unlines y)

Thanks:)

Mark Seemann
  • 225,310
  • 48
  • 427
  • 736
yam
  • 35
  • 4

2 Answers2

9

The writeFile function has the type FilePath -> String -> IO (), which means that it must run in the IO context.

It doesn't have to run in the main function, but any function that involves IO, including writeFile, will have a return type that involves IO. So you could definitely do something like this:

myFunc :: String -> IO ()
myFunc contents = do
  -- do something else
  writeFile "foo.txt" contents
  -- do more stuff here

You can't, however, call functions that return IO a from pure functions (or, rather, you can't extract the value from the IO container). That's by design; it's how Haskell works, and it's a good thing. If you want to enable users to perform impure actions at arbitrary times, you must design for such a feature. Here's my introduction to pure interactions - that's one place to start.

Mark Seemann
  • 225,310
  • 48
  • 427
  • 736
7

Yes, you can use writeFile in other places than main, but for a place to qualify, the type IO has to be a part of that place's type signature. (The reason I'm saying place is because main isn't a function, but your concat is a function. And the place you want to look at putting your writeFile call has to be an IO action, which can be the result of a function or not.)

You mentioned saving something related to a game into a .txt file. An example of that could be:

saveGame :: FilePath -> GameState -> IO ()
saveGame gameFile gameState =
  writeFile gameFile (serializeGame gameState)

serializeGame :: GameState -> String
serializeGame (GameState ...) = ...

runGame :: GameState -> IO ()
runGame gameState = do
  ...
  if wantsToSaveGame
    then saveGame gameFile gameState
    else ...
  ...
  runGame updatedGameState

main :: IO ()
main = do
  ...
  runGame initialGameState

In this contrived example, serializeGame would not be a suitable place to call saveGame because it's a pure function, whereas runGame is a self-recursive IO () action capable of affecting files on your file system.

An example of a related IO action that isn't a function could be this one:

resetSaveGame :: IO ()
resetSaveGame =
  saveGame defaultGameFile initialGameState
sshine
  • 15,635
  • 1
  • 41
  • 66