0
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?

Gilgamesz
  • 4,727
  • 3
  • 28
  • 63
  • 2
    Can you explain a bit more why you think there'd be a problem? Why would `arg`'s type matter? Do you think `args` is the return value of `main`? – sepp2k Oct 29 '16 at 23:07
  • 1
    Why do you think it should raise an error? If the type of `getArgs` is `IO [String]`, then the type of `args` in `do { args <- getArgs; ... }` is `[String]`. Then `case args of { .. }` must have type `IO ()` because it is the final statement in the do block, which has type `IO ()` (due to the annotation). Then each pattern in the case must bind something of type `[String]` and produce an `IO ()`. Clearly `[]` is a pattern of type `[String]` and `putStrLn .. :: IO ()`. This is exactly what the typechecker does. – user2407038 Oct 29 '16 at 23:08
  • 1
    The type of a `do` block is the type of the _last_ statement -- in this case, of `case ...` which has the same type of `putStrLn ".."` which is `IO ()`. – chi Oct 30 '16 at 07:31

2 Answers2

6

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

Community
  • 1
  • 1
baxbaxwalanuksiwe
  • 1,474
  • 10
  • 20
  • @Gilgamesz I'm sorry I can't see what you edited in your post :/ – baxbaxwalanuksiwe Oct 30 '16 at 11:14
  • I missed to click: EDIT ;). – Gilgamesz Oct 30 '16 at 11:17
  • Ok, I found the problem in my reasoning: "`getArgs` is expected to have type `C [String]`, but it has type `IO [String]`". Why expected type is `C [String]`? After all, The type of a do block is the type of the last statement, so it should be ok. – Gilgamesz Oct 30 '16 at 19:11
  • @Gilgamesz if you are in the context of a monad, you can only run monadic action wrapped in that monad. Here, if your function has type `C ()`, you can only write things such as `x <- action` if `action` has type `C something`, so because `getArgs` has type `IO [String]`, you can't directly call it from `main`, you first have to wrap the `IO` action into your `C` monad, here, with `lift` or `liftIO` – baxbaxwalanuksiwe Oct 31 '16 at 21:39
4

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.

Jon Purdy
  • 53,300
  • 8
  • 96
  • 166