I have a Logger type of kind * -> *
which can take any type and log the value in a file. I am trying to implement this in a monadic way so that I log and keep working the same. My code looks like
import Control.Applicative
import Control.Monad
import System.IO
import Control.Monad.IO.Class
instance Functor Logger where
fmap = liftM
instance Applicative Logger where
pure = return
(<*>) = ap
newtype Logger a = Logger a deriving (Show)
instance Monad (Logger) where
return = Logger
Logger logStr >>= f = f logStr
instance MonadIO (Logger) where
liftIO a = do
b <- liftIO a
return b
logContent :: (Show a) => a -> Logger a
logContent a = do
b <- liftIO $ logContent2 a
return b
logContent2 :: (Show a) => a -> IO a
logContent2 a = do
fHandle <- openFile "test.log" AppendMode
hPrint fHandle a
hClose fHandle
return (a)
The liftIO function goes on endless loop as it calls itself. I am not able to do b <- a either. Can someone help on getting MonadIO implementation right ?
As noted in the comments, I think you've misunderstood what
MonadIO
andliftIO
do.These typeclasses and functions come from
mtl
library. Rather unfortunately,mtl
stands for "monad transformer library", but mtl is not a monad transformer library. Rather,mtl
is a set of typeclasses that allow you to take a monad that --- and this is important --- already has a particular type of functionality and provide that monad with a consistent interface around that functionality. This ends up being really useful for working with actual monad transformers. That's becausemtl
allows you to usetell
andask
andput
to access theWriter
,Reader
, andState
functionality of your monad transformer stack in a consistent way.Separately from this transformer business, if you already have a custom monad, say that supports arbitrary IO and has
State
functionality, then you can define aMonadState
instance to make the standard state operations (state
,get
,gets
,put
,modify
) available for your custom monad, and you can define aMonadIO
instance to allow an arbitrary IO action to be executed in your custom monad usingliftIO
. However, none of these typeclasses are capable of adding functionality to a monad that it doesn't already have. In particular, you can't transform an arbitrary monadic actionm a
into anIO a
using aMonadIO
instance.Note that the
transformers
package contains types that are capable of adding functionality to a monad that it doesn't already have (e.g., adding reader or writer functionality), but there is no transformer to addIO
to an arbitrary monad. Such a transformer would be impossible (without unsafe or nonterminating operations).Also note that the signature for
liftIO :: MonadIO m => IO a -> m a
puts aMonadIO
constraint onm
, and this isn't just a trivial constraint. It actually indicates thatliftIO
only works for monadsm
that already have IO functionality, so eitherm
is the IO monad, or it's a monad stack withIO
at its base. YourLogger
example doesn't have IO functionality and so can't have a (sensible)MonadIO
instance.Getting back to your specific problem, it's actually a little bit hard to steer you right here without knowing exactly what you're trying to do. If you just want to add file-based logging to an existing IO computation, then defining a new transformer stack will probably do the trick:
and you can write things like:
The need to add
liftIO
calls when using IO actions within theLogIO
monad is ugly but largely unavoidable.This solution would also work for adding file-based logging to pure computations, with the understanding that you have to convert them to IO computations anyway if you want to safely log to a file.
The more general solution is to define your own monad transformer (not merely your own monad), like
LoggerT m
, together with an associatedMonadLogger
type class that will add file-based logging to to any IO-capable monad stack. The idea would be that you could then create arbitrary custom monad stacks:and then write code that mixes monadic computations from different layers (like mixing state computations and file-based logging):
Is this what you what you're trying to do? If not, maybe you could describe, either here or in a new question, how you're trying to add logging to some example code.