I try to understand the lifting principle by example, and found this:
Then, if I change it's lift (lift (putStrLn "bla-bla"))
to putStrLn "bla-bla"
, the compiler throw error!
I do this base on my understanding: do
block are just syntax sugar, and each line's result are pass into next line. If the next line do not use the args that passed from previous line, the type of args won't cause type confliction, I think.
Take bellow as example, while x<- getLine
can past compiling
test:: IO ()
test = do
x <- getLine -- discarded and compiler don't care it type
let a = "bla-bla" -- discarded and compiler don't care it type
putStrLn $ "You type are discarded: "
Now back to the calculations
function:
type Data = Map.Map Int String
type StateIO = StateT Data IO
type MaybeStateIO a = MaybeT StateIO a
calculations :: MaybeStateIO ()
calculations = do
lift (lift (putStrLn "bla-bla")) -- if I change this to `putStrLn "bla-bla"`, it failed compiling.
lift (modify (Map.insert 3 "3"))
lift (modify (Map.insert 1 "1"))
mb <- lift (get >>= (return . Map.lookup 1))
lift (lift (print mb))
main = runStateT (runMaybeT calculations) Map.empty
I don't understand is that to compiler require lifting on putStrLn "bla-bla"
.
Isn't it enough when return value of the last line of do
block match the function's return value?
In this example, how does the compiler decide the do block
's value type? according to function's signature?
Can anyone explains the lift for me? How do it work, when to use, etc...
Thanks.
CodePudding user response:
The value of calculations
is a MaybeStateIO
value. That's the monad you are operating in, so that's what every line of the do
block has to produce. But putStrLn "bla-bla"
does not produce a MaybeStateIO
value; it just produces an IO
value. The first lift
takes that IO
value and returns a StateIO
value; the second lift
takes that StateIO
value and returns a MaybeStateIO
value.
Remember,
do
a
b
is just syntactic sugar for a >> b
, and (>>) :: Monad m => m a -> m b -> m b
needs values from the same monad as arguments. It's only the "return value" (a
and b
) of the monad that can vary from line to line; the monad m
itself is fixed.
CodePudding user response:
Isn't it enough when return value of the last line of
do
block match the function's return value?
No, since that would mean that you could write a do
block where the first item for example would use the instance of []
for Monad
whereas the next would use for example Maybe
or IO
, but then how would x <- some_list
make sense for a list of putStrLn x
? All lines in the do
block should be of type m a
with m
the same instance of Monad
, and the a
s can have different types for each line. If you write a do block with:
foo = do x <- exp1 exp2
then this is translated to exp1 >>= \x -> exp2
, and since (>>=) :: Monad m => m a -> (a -> m b) -> m b
operates where the two operands share the same monad m
, this thus means that exp1 :: m a
and exp2 :: m b
thus need to work with the same monadic type m
.
You require to perform lifting twice since the line should have as type MaybeT (StateT Data IO) a
whereas putStrLn "bla-bla"
has IO a
, it thus requires one lift :: (MonadTrans t, Monad m) => m a -> t m a
to lift it to StateT Data IO a
and another to finally lift it to a MaybeT (StateT Data IO) a
.