k0001 / di

Easy and powerful typeful logging without monad towers, in Haskell.
26 stars 5 forks source link

Add MonadDi for sync and async logging functions #1

Closed k0001 closed 7 years ago

k0001 commented 7 years ago

Di needs IO to work, we acknowledge that, but sometimes the Monad on which you are trying to use Di deliberately doesn't support MonadIO, likely so that it can provide a safe interface and prevent arbitrary IO for being called.

We propose changing functions like err :: MonadIO m => Di path msg -> msg -> m () to err :: MonadDi m => Di path msg -> msg -> m (), where:

class MonadDi (m :: * -> *) where
   liftLog :: Log -> m ()
   default liftLog :: MonadIO m => Log -> m ()
   liftLog = liftIO . runLog

newtype Log = UnsafeLog (IO ())  -- ^ Constructor not exported

runLog :: Log -> IO ()
runLog (Log m) = m 

The cost of doing this, of course, is that now we need to provide MonadDi instances for every monad transformer. We can do that for all of the ones in transformers.

Thanks @shlevy for the suggestion

k0001 commented 7 years ago

liftLog is a terrible name.

k0001 commented 7 years ago

I've decided against this.

The reason is that the only function where this matters is log, and if you do have some IO-based Monad Foo that doesn't implement MonadIO, then you can just do:

fooLog :: Monoid path => Di level path msg -> level -> msg -> Foo ()
fooLog = \di l m -> UnsafeFooWhatever (liftIO (Di.log di l m))

Doing this is cheaper that having to maintain a new MonadXxx typeclass which would need a new instance for every new monad.