Search code examples
haskellioprogram-entry-pointio-monaddo-notation

Can I have separate functions for reading and writing to a txt file in Haskell, without using a 'main' function?


I'm making a program using Haskell that requires simple save and load functions. When I call the save function, I need to put a string into a text file. When I call load, I need to pull the string out of the text file.

I'm aware of the complexities surrounding IO in Haskell. From some reading around online I have discovered that it is possible through a 'main' function. However, I seem to only be able to implement either save, or load... not both.

For example, I have the following function at the moment for reading from the file.

main = do  
 contents <- readFile "Test.txt"  
 putStrLn contents  

How can I also implement a write function? Does it have to be within the same function? Or can I separate it? Also, is there a way of me being able to name the functions load/save? Having to call 'main' when I actually want to call 'load' or 'save' is rather annoying.

I can't find any examples online of someone implementing both, and any implementations I've found of either always go through a main function.

Any advice will be greatly appreciated.


Solution

  • I'm aware of the complexities surrounding IO in Haskell.

    It's actually not that complex. It might seem a little intimidating at first but you'll quickly get the hang of it.

    How can I also implement a write function?

    The same way

    Or can I separate it?

    Yes

    Also, is there a way of me being able to name the functions load/save?

    Yes, for example you could do your loading like this:

    load :: IO String
    load = readFile "Test.txt"
    

    All Haskell programs start inside main, but they don't have to stay there, so you can use it like this:

    main :: IO ()
    main = do
      contents <- load -- notice we're using the thing we just defined above
      putStrLn contents
    

    Note the main is always what your program does; But your main doesn't only have to do a single thing. It could just as well do many things, including for instance reading a value and then deciding what to do; Here's a more complicated (complete) example - I expect you'll not understand all parts of it right off the bat, but it at least should give you something to play around with:

    data Choice = Save | Load
    
    pickSaveOrLoad :: IO Choice
    pickSaveOrLoad = do
       putStr "Do you want to save or load? "
       answer <- getLine
       case answer of
         "save" -> return Save
         "load" -> return Load
         _      -> do
           putStrLn "Invalid choice (must pick 'save' or 'load')"
           pickSaveOrLoad
    
    save :: IO ()
    save = do
      putStrLn "You picked save"
      putStrLn "<put your saving stuff here>"
    
    load :: IO ()
    load = do
      putStrLn "You picked load"
      putStrLn "<put your loading stuff here>"
    
    main :: IO ()
    main = do
      choice <- pickSaveOrLoad
      case choice of
        Save -> save
        Load -> load
    

    Of course it's a bit odd to want to do either save or load, most programs that can do these things want to do both, but I don't know what exactly you're going for so I kept it generic.