Gilgamesz
Gilgamesz

Reputation: 5073

Different type of function and `in do statement`

main :: IO ()
main = do
  args <- getArgs
  case args of
    [] -> putStrLn "Give an argument!"

It works, but why? Returned type is IO (). getArgs returned IO [String] ( not IO ()). So it should raise an error.


type C = ErrorT String IO
main :: C ()
main = do
    args <- getArgs
    case args of
      [] -> putStrLn "Give an argument!"

putStrLn tries to return IO (). But it isn't compatible with C. So, should I liftIO? I see that I should. Could you show me how does liftIO works here? Why I have to use liftIO with getArgs?

Upvotes: 0

Views: 103

Answers (2)

baxbaxwalanuksiwe
baxbaxwalanuksiwe

Reputation: 1494

To figure out what is going on, you can try de-sugaring you function:

main :: IO ()
main = getArgs >>= \ args ->
    case args of
        [] -> putStrLn "Give an argument!"

Here, getArgs has type IO [String] and the function (let's call it f) has type [String] -> IO (). Now lets see what's going on with (>>=):

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

-- a unifies with [String] and m with IO
(getArgs >>=) :: ([String] -> IO b) -> IO b
(>>=) :: IO [String] -> ([String] -> IO b) -> IO b

-- b unifies with ()
getArgs >>= f :: IO ()
(>>=) :: IO [String] -> ([String] -> IO ()) -> IO ()

For your example with C, there are three things wrong:

  • GHC expects main to have type IO (), but it has type C () in your snippet
  • getArgs is expected to have type C [String], but it has type IO [String]
  • same for putStrLn, which must have type C ()

To fix this, since ErrorT is a monad transformer, you can lift a monadic action to the level of the transformer with lift.

liftIO is a lift operation that can be applied only with monad transforming IO. The interesting thing is that it can work with any monad transformer (with as much monad transformer depth as you want): With liftIO, you can work with T1 (T2 (... (Tn IO)...)) a for any n where Tn are monad transformers.

You can notice their kind of similar signatures:

lift :: (MonadTrans t, Monad m) => m a -> t m a
liftIO :: MonadIO m => IO a -> m a

You don't need to specify IO for m, in the signature of liftIO, because it's instance of MonadIO already tells us that it can perform only IO actions.

To conclude, this is how you would use liftIO in your program:

type C = ErrorT String IO
main :: IO ()
main = runErrorT go >>= print  -- print the result of type `Either String ()`
    where go :: C ()
          go = do
              args <- liftIO getArgs
              case args of
                [] -> liftIO $ putStrLn "Give an argument!"

But, here, since you don't use any features of the ErrorT monad, the code is not really a good example.

Also, note that if you are talking about ErrorT in the mtl package, it is deprecated, consider using ExceptT.

Check out this post for more information about lift vs liftIO

Upvotes: 6

Jon Purdy
Jon Purdy

Reputation: 55039

If you desugar the do notation:

main = getArgs >>= (\ args -> case args of [] -> putStrLn "Argh!")

getArgs has type IO [String]. >>= pipes the resulting [String] into the lambda, which returns IO (), the result of putStrLn "...". Here, >>= has the inferred type:

(>>=) :: IO [String] -> ([String] -> IO ()) -> IO ()

So the type of the whole expression is IO (), matching the signature of main.

Upvotes: 4

Related Questions