Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Try removing MonadIO superclass #119

Open
wants to merge 1 commit into
base: master
Choose a base branch
from

Conversation

MichaelXavier
Copy link
Collaborator

This superclass isn't really needed by the Katip class
itself. Basically it prevents you from adding a MonadIO constraint if
you do use logging, but:

  1. You get confusing redundant constraint errors when you also have a
    MonadIO constraint.
  2. It forces you to add MonadIO in some cases where you're not
    actually logging.

I had 10 minutes to kill so I thought I'd see how hard it'd be to
remove.

This is in reference to #88

@ozataman LMK if you're okay with this.

This superclass isn't really needed by the Katip class
itself. Basically it prevents you from adding a MonadIO constraint if
you do use logging, but:

1. You get confusing redundant constraint errors when you also have a
MonadIO constraint.
2. It forces you to add MonadIO in some cases where you're not
actually logging.

I had 10 minutes to kill so I thought I'd see how hard it'd be to
remove.

This is in reference to #88
@YPares
Copy link

YPares commented Apr 28, 2020

Also there is lots of pure core, or code just using Reader/State which you want to do logging from as it computes (ie not log everything once it is done computing), but you don't want it to be able to access IO. I strongly second that PR.

@ozataman
Copy link
Member

ozataman commented May 8, 2020

I don't disagree with the spirit of this change and it seems to be generally harmless. Further, delaying the superclass constraint in general makes sense, so I also support this change.

However, since all logging actions get the MonadIO constraint instead, I imagine the inconvenience will persist. What's a case this enables that isn't currently possible?

It may be worth deliberating whether we can take a typeclass approach on the actions themselves - e.g. one of them could be a typeclass member, enabling all others to automatically inherit. This way, you could have logging actions that don't need MonadIO in every case.

@tbidne
Copy link
Contributor

tbidne commented Oct 22, 2021

I agree that this would be nice to have. I like isolating effects (e.g. MTL-style), which is a bit pointless in the presence of MonadIO. In my case, I was able to work around this by providing my own wrapper class and writing my logic in terms of that instead. E.g.

-- copy Katip functions we want here
class Monad m => MonadLogger m where
  logFm :: Severity -> LogStr -> m ()
  addNamespace :: Namespace -> m a -> m a
  ...

-- AppT has instances for Katip, KatipContext
instance MonadIO m => MonadLogger (AppT e m) where
  logFm = Katip.logFM
  ...

-- No more MonadIO!
foo :: MonadLogger m => m ()
foo = do
  logFm InfoS (LogStr "Info...")
  ...

This isn't too bad, though it's pretty boilerplate-heavy. It would be nice if this wasn't necessary, but if the MonadIO constraint simply moves to the functions, e.g., logFM then this pattern would probably still be required.

@jmorag
Copy link

jmorag commented May 20, 2023

A tangible benefit of this change is that it makes integration with effect systems like effectful easier. For example, with a KatipContextE effect analogous to the KatipContextT transformer, I'd want to define an instance for the KatipContext class like so, but can't.

import Effectful
import Effectful.Dispatch.Static
import Katip.Monadic

data KatipContextE :: Effect
type instance DispatchOf KatipContextE = 'Static 'WithSideEffects
newtype instance StaticRep KatipContextE = KatipContextE {unKatipContextE :: KatipContextTState}

-- These don't compile with MonadIO as a superclass of Katip and KatipContext
instance (KatipContextE :> es) => Katip (Eff es) where
  getLogEnv = ltsLogEnv . unKatipContextE <$> getStaticRep
  localLogEnv f = localStaticRep $ \(KatipContextE lts) -> KatipContextE (lts {ltsLogEnv = f (ltsLogEnv lts)})

instance (KatipContextE :> es) => KatipContext (Eff es) where
  getKatipContext = ltsContext . unKatipContextE <$> getStaticRep
  getKatipNamespace = ltsNamespace . unKatipContextE <$> getStaticRep
  localKatipContext f = localStaticRep $
    \(KatipContextE lts) -> KatipContextE (lts {ltsContext = f (ltsContext lts)})
  localKatipNamespace f = localStaticRep $
    \(KatipContextE lts) -> KatipContextE (lts {ltsNamespace = f (ltsNamespace lts)})

Moving MonadIO to the functions that need it would also allow the effectful implementation to use katipNoLogging, katipAddContext, and katipAddNamespace unchanged.

@MichaelXavier
Copy link
Collaborator Author

@ozataman It seems like from the commentary here that there are a few cases here that would benefit from deferring the MonadIO. It seems like we could adopt this approach pretty harmlessly now and then leave any further abstraction for another day should the need arise. What do you think?

@CharlesEkkel
Copy link

I'd just like to add that I ran into this issue myself when trying to integrate Katip into my setup using Effectful, as @jmorag indicated. Is there anything blocking this PR that I can help with?

Copy link

@arybczak arybczak left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

FYI there's now https://hackage.haskell.org/package/katip-effectful that would benefit from this PR being merged, then at least the author wouldn't have to overwrite class methods with versions that don't include the MonadIO constraint.

@@ -811,12 +811,12 @@ closeScribes le = do
-- of the log env that are reverted when the supplied monad
-- completes. 'katipNoLogging', for example, uses this to temporarily
-- pause log outputs.
class MonadIO m => Katip m where
class Katip m where

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should really be

Suggested change
class Katip m where
class Monad m => Katip m where

then you won't have to include these Monad constraints below.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@arybczak is that preferable though? I thought part of the idea of this change was to not require unused constraints. It makes sense to me if an implementation requires Monad that it should demand it but the base class doesn't seem to demand it. Maybe I'm missing something.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I thought part of the idea of this change was to not require unused constraints

The problem isn't really unused constraints, it's just that you're forcing MonadIO on downstream users of Katip and MonadIO is huge (in a sense of what you can do with it). Monad on the other hand isn't, so including it as a superclass allows you to omit writing (Katip m, Monad m) in type signatures (which is somewhat annoying) and doesn't give any practical downsides.

Copy link

@arybczak arybczak left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Btw, full "de-IOnification" would require you putting some of the generic logging functions into the Katip or KatipContext class so they also don't require MonadIO constraint, but that is further API breaking.

@MichaelXavier You can have a look at https://hackage.haskell.org/package/log-base-0.12.0.1/docs/Log-Class.html for comparison - MonadLog defines generic logging operations and then there are some specific logging helper functions, but you don't need MonadIO in scope to call them because they just use MonadLog methods.

@@ -511,7 +511,7 @@ instance MonadIO m => KatipContext (NoLoggingT m) where

-- | Convenience function for when you have to integrate with a third
-- party API that takes a generic logging function as an argument.
askLoggerIO :: (Applicative m, KatipContext m) => m (Severity -> LogStr -> IO ())
askLoggerIO :: (Applicative m, KatipContext m, MonadIO m) => m (Severity -> LogStr -> IO ())

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think this requires MonadIO, right? You return an IO function, but the constraint doesn't seem needed to do that.

It looks similar to https://hackage.haskell.org/package/log-base-0.12.0.1/docs/Log-Monad.html#v:getLoggerIO.

@@ -811,12 +811,12 @@ closeScribes le = do
-- of the log env that are reverted when the supplied monad
-- completes. 'katipNoLogging', for example, uses this to temporarily
-- pause log outputs.
class MonadIO m => Katip m where
class Katip m where

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I thought part of the idea of this change was to not require unused constraints

The problem isn't really unused constraints, it's just that you're forcing MonadIO on downstream users of Katip and MonadIO is huge (in a sense of what you can do with it). Monad on the other hand isn't, so including it as a superclass allows you to omit writing (Katip m, Monad m) in type signatures (which is somewhat annoying) and doesn't give any practical downsides.

@@ -937,7 +937,7 @@ katipNoLogging = localLogEnv (\le -> set logEnvScribes mempty le)
-- | Log with everything, including a source code location. This is
-- very low level and you typically can use 'logT' in its place.
logItem
:: (A.Applicative m, LogItem a, Katip m)
:: (A.Applicative m, LogItem a, Katip m, MonadIO m)
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why still require Applicative m? Shouldn't that be implied by MonadIO?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

8 participants