Haskell: how to use haskeline and write to a file in the same program

735 views Asked by At

I wrote a program in Haskell that builds a guitar tab as a txt file in the current directory. It gets a String of chords from the user, and then builds the proper output and writes it line by line to a file.

I wasn't able to use the backspace key on my input when I was using getLine, because it would print a bunch of gibberish to the screen.

I'm trying to use haskeline to fix this, and I commented out the bulk of my main method in the meantime so that each change requires less editing (every command I commented out in 'main' is of the same type as the single command I kept, so if I can get this simplified version to work, the whole thing should work). Basically, I need to be able to get the input from the user using haskeline, but then I also need to run some "side effects" commands in my "do" block after I do that.

I'm new to Haskell and I don't fully understand what is and is not allowed or why. Here's the simplified version of my program:

import Data.List
import System.Console.Haskeline

main = runInputT defaultSettings loop
 where
   loop :: InputT IO ()
   loop  = do
     name <- getInputLine "Enter name of song: "
     case name of
       Nothing -> return ()
       Just songName -> return ()
     chords <- getInputLine "Enter chords to be tabified "
     case chords of
       Nothing -> do outputStrLn $ "No chords entered. Exiting."
       Just chords -> do
                        writeFile "./test.txt" "did it work?"
                        return ()

I got all of this syntax straight from a Haskeline tutorial. I tried running it without making any changes first and it worked, so I know that it's all correct -except- for the last 3 lines that I edited, where I have the "do" block and am trying to call "writeFile" before "return()".

I know that the type of "loop" has to be InputT IO () in order to use getInputLine (the haskeline version of getLine), but I don't know how to accomplish "side effects" like writing to a file at the same time.

When I try to load my project in ghci, I get the following error:

error:
-Couldn't match type 'IO' with 'InputT IO'
 Expected type: InputT IO ()
   Actual type: IO ()
- In a stmt of a 'd' block: writeFile "./test.txt" "did it work?"
  In the expression:
    do { writeFile "./test.txt" "did it work?";
         return () }
  In a case alternative:
    Just chords
      -> do { writeFile "./test.txt" "did it work?";
              return () }

Failed, modules loaded: none.
2

There are 2 answers

3
chi On BEST ANSWER

Since InputT IO is an instance of MonadIO, you can run any IO action by lifting it to a InputT IO action, using

liftIO :: IO a -> InputT IO a

Indeed, this is the standard way to "run IO" in moands that support IO but aren't IO.

3
John F. Miller On

InputT is an instance of MonadTrans so

Just chords -> lift $ do

EDIT:

lift is in Control.Monad.Trans.Class. (Hat tip: Jon Purdy)