readInputLine with IO String

207 views Asked by At

I would like to read an input string with a custom prompt, however the prompt string comes from impure context, thus I can't use readInputLine as is. I've tried to implement a function based on this answer

getLineIO :: MonadException m => IO String -> InputT m (Maybe String)
getLineIO ios = do
    s <- ios
    res <- getInputLine s
    lift res

but I get an error

   Couldn't match expected type ‘InputT m String’
                with actual type ‘IO String’
    Relevant bindings include
      getLineIO :: IO String -> InputT m (Maybe String)
        (bound at Main.hs:38:1)
    In a stmt of a 'do' block: s <- ios
    In the expression:
      do { s <- ios;
           return $ getInputLine s }

Update: got it to work based on @bheklilr's answer

getLineIO :: (MonadException m, MonadIO m) => IO String -> InputT m (Maybe String)
getLineIO ios = do
      s <- liftIO ios
      getInputLine s
1

There are 1 answers

2
bheklilr On BEST ANSWER

The code

do
    s <- ios
    res <- getInputLine s
    lift res

Gets de-sugared into

ios >>= \s -> (getInputLine s >>= \res -> lift res)

Where

(>>=) :: Monad m => m a -> (a -> m b) -> m b

This type signature means that m has to be the same Monad instance throughout. You've given it ios :: IO String and \s -> getInputLine s :: String -> InputT n (Maybe String), but m can't be both IO and InputT n, hence the compiler error.

You can just use liftIO on ios provided instance MonadIO m => MonadIO (InputT m) is defined, which it is. So you can just do

getLineIO :: (MonadException m) => IO String -> InputT m (Maybe String)
getLineIO ios = do
    s <- liftIO ios
    res <- getInputLine s
    lift res