2

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.
BabaSvoloch
  • 301
  • 3
  • 20

2 Answers2

4

InputT is an instance of MonadTrans so

Just chords -> lift $ do

EDIT:

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

John F. Miller
  • 26,961
  • 10
  • 71
  • 121
  • I tried adding that to my project, before my "writeFile" and "return ()" lines, and I get this error: "Variable not in scope: lift :: IO () -> InputT IO ()" – BabaSvoloch Mar 19 '19 at 17:44
  • I thought maybe the error meant I needed to import a library to use "lift", but I've looked everywhere and I can't find it. There's libraries I found like Control.Applicative but that's using something called "LiftA" and I doubt it's the same thing. Do you have any ideas? – BabaSvoloch Mar 19 '19 at 17:57
  • 1
    @BabaSvoloch: For `lift` you want [`Control.Monad.Trans.Class`](https://hackage.haskell.org/package/transformers-0.5.6.2/docs/Control-Monad-Trans-Class.html#t:MonadTrans) from `transformers`; for `liftIO` it’s [`Control.Monad.IO.Class`](https://hackage.haskell.org/package/base-4.12.0.0/docs/Control-Monad-IO-Class.html#t:MonadIO) which used to be in `transformers` but is now in `base`. You can find these things more easily with [Hoogle](https://www.stackage.org/lts-13.13/hoogle?q=lift). – Jon Purdy Mar 19 '19 at 21:33
4

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.

chi
  • 111,837
  • 3
  • 133
  • 218
  • Hi @chi , thank you so much for your explanation. I'm still having trouble getting my example from above to run; I think I'm messing up the syntax somehow. If it's not too much trouble, can you show me how I could incorporate this into my program? – BabaSvoloch Mar 19 '19 at 17:50
  • @BabaSvoloch You need to check the types of your actions. E.g. `writeFile ...` returns `IO ()`, so you need to use `liftIO (writeFile ...)` to turn it into an `InputT IO ()`. Instead `getInputLine ...` should be already OK -- no lifting needed. – chi Mar 19 '19 at 18:01
  • 1
    Thank you, I got it working! My problem was that when I tried using "liftIO", ghci suggested that I meant "liftIOOp" from the haskeline library, and I thought it was the same function and that the haskeline library had been updated recently. I didn't realize liftIO wasn't from the haskeline library and that I needed to import Control.Monad.IO.Class – BabaSvoloch Mar 19 '19 at 18:26