Avoiding lift with monad transformers

前端 未结 2 1853
一个人的身影
一个人的身影 2021-01-29 22:36

I have a problem to which a stack of monad transformers (or even one monad transformer) over IO. Everything is good, except that using lift before every action is t

2条回答
  •  走了就别回头了
    2021-01-29 23:15

    You can make your functions monad-agnostic by using typeclasses instead of concrete monad stacks.

    Let's say that you have this function, for example:

    bangMe :: State String ()
    bangMe = do
      str <- get
      put $ str ++ "!"
      -- or just modify (++"!")
    

    Of course, you realize that it works as a transformer as well, so one could write:

    bangMe :: Monad m => StateT String m ()
    

    However, if you have a function that uses a different stack, let's say ReaderT [String] (StateT String IO) () or whatever, you'll have to use the dreaded lift function! So how is that avoided?

    The trick is to make the function signature even more generic, so that it says that the State monad can appear anywhere in the monad stack. This is done like this:

    bangMe :: MonadState String m => m ()
    

    This forces m to be a monad that supports state (virtually) anywhere in the monad stack, and the function will thus work without lifting for any such stack.

    There's one problem, though; since IO isn't part of the mtl, it doesn't have a transformer (e.g. IOT) nor a handy type class per default. So what should you do when you want to lift IO actions arbitrarily?

    To the rescue comes MonadIO! It behaves almost identically to MonadState, MonadReader etc, the only difference being that it has a slightly different lifting mechanism. It works like this: you can take any IO action, and use liftIO to turn it into a monad agnostic version. So:

    action :: IO ()
    liftIO action :: MonadIO m => m ()
    

    By transforming all of the monadic actions you wish to use in this way, you can intertwine monads as much as you want without any tedious lifting.

提交回复
热议问题