This post is mean to explain the differences between the 3. These 3 terms come up regularly and it is important to differentiate them.
Requirement
This post aims for the pre-intermediate haskell programmers that have already learn the fundamental concept (monad, functor ...) and have written some haskell program already.
Monad Transformer
monad-transformer is a type container such as ReaderT, WriterT ... We will focus on the use-case only.
When people refer to monad-transformer they usually mean something their monad-transformer stack: newtype AppM a = ReaderT Env (LoggingT IO) a
The reason why haskell application use this stack over just IO () because monad-transformer provide extra functionality for example ReaderT provide ask function to get env variable instead of passing it in all the functions.
MTL - Monad Transformer Library
mtl library define various typeclasses that help working with monad-transformer. Remember that mtl is a library not a monad-transformer.
The reason why mtl is created because when we define a function to work with monad-transformer stack, we do not want to use AppM in the function type.
For example:
- instead of this
askEnvAndReadFile :: FilePath -> AppM String - we want to use this
askEnvAndReadFile :: (MonadReader Env m, MonadIO m) -> FilePath -> m String
MonadReader and MonadIO are typeclasses that show that the function can ask for Env and can do IO action. This function can be use within any monad-trasnformer stack that has ReaderT and IO in them, not just our predefined AppM.
MTL-style (tagless final)
Sometimes when mtl is being discussed, it is not about the mtl library itself but the techniques that mtl library uses which is the Tagless Final. I will not provide detail explanation of this, but basically this is a technique that help haskell programmer write function with business model constraint.
For example, we want to define a function that can manage certain resources (make api call to get the resources). We do not want the function to be able to do any IO action.
Below show a function that can only make http call and log, not any other action
app :: (ManageResource m, HasLogging m) -> m ()
app = do
result <- makeApiCall
log (show result)
pure ()
Notice that ManageResource and HasLogging is a typeclass that has nothing to do with mtl library, but instead we define this by ourself:
class (Monad m) => ManageResource m where
makeApiCall :: m Resource
This is just an interface only, the implementation makeApiCallIO will have MonadIO constraint or return IO monad: makeApiCallIO :: IO Resource).
So when we want to use this function with our stack AppM we can define an instance for this ManageResource typeclass.
instance ManageResource AppM where
makeApiCall = makeApiCallIO
The useful thing of using typeclass constraints instead of concrete type such as AppM is that we can use a mock stack MockM where we do not actually do any IO action.
Example:
makeApiCallMock :: m Resource
makeApiCallMock = pure someMockData
instance ManageResource MockM where
makeApiCall = makeApiCallMock
Conclusion
As we can see, the 3 terms are different. The most important thing is the mtl-style technique that is used a lot in realworld haskell application (HasDatabase, HasLogging ...). It is important to be able to read other people constraint typeclasses as well as create our own. I was confused to this 3 terms as well before, hope this clears up some misunderstanding.
Top comments (0)