Reputation: 5073
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
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:
main
to have type IO ()
, but it has type C ()
in your snippetgetArgs
is expected to have type C [String]
, but it has type IO [String]
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
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