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

Remove the Has type-class #4

Merged
merged 10 commits into from
Aug 9, 2018
Merged

Remove the Has type-class #4

merged 10 commits into from
Aug 9, 2018

Conversation

aherrmann
Copy link
Member

  • Remove Has
  • Remove HasReader/HasState instances for ReaderT/StateT.
  • Derive HasReader/HasState from mtl's MonadReader/MonadState using the newtypes with the same name.
  • Introduce Field to zoom in on record fields in HasReader/HasState.

@aherrmann aherrmann requested a review from aspiwack August 9, 2018 14:30
Copy link
Member

@aspiwack aspiwack left a comment

Choose a reason for hiding this comment

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

A few comments. Address the trivial one now, make issues for the other ones. Then merge.

import Data.IORef
import GHC.Generics (Generic)

import Has
import Accessors
Copy link
Member

Choose a reason for hiding this comment

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

You should just re-export Accessors from the HasReader and HasWriter. Maybe renaming it to Internal.Accessors.

(Field "countCtx" (MonadReader (ReaderT CountLogCtx m)))))
-- XXX: This requires @Field@ and @MonadReader@ to have @MonadIO@ instances.
-- That seems anti-modular - if a user-defined constraint is required,
-- they may have to add orphan instances for @Field@ and @MonadReader@.
Copy link
Member

Choose a reason for hiding this comment

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

This is a very interesting point. I think that it is fair to lift MonadIO through each of our newtype things (since, well, we are providing combinators using it 🙂 ). Maybe a couple of others (like MonadPrim). But there are arbitrarily many classes which may be relevant.

If a library user makes a new combinator MonadFoo which uses an instances of the class MonadFoo, to build a HasState capability from some other capability, somehow, then they may need to lift MonadFoo through each of our newtypes. Which is probably orphan. Hence a tad fragile.

There is probably not a way around this. But probably, at least every class we mention in the library should be systematically derived for all our newtypes.

@@ -39,7 +38,7 @@ class Monad m => Logger m where
logStr :: String -> m ()

-- | Any @HasReader "logger" (String -> IO ())@ can be a @Logger@.
newtype TheLoggerReader m a = TheLoggerReader (m a)
newtype TheLoggerReader (m :: * -> *) a = TheLoggerReader (m a)
Copy link
Member

Choose a reason for hiding this comment

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

I'm curious why the kind annotation was needed.

Copy link
Member Author

Choose a reason for hiding this comment

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

That was an interaction between PolyKinds DerivingVia. Without annotation m would have kind k -> * which made derive via fail. However, PolyKinds is no longer required in the example module, so the annotations can be dropped.

Copy link
Member

Choose a reason for hiding this comment

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

For clarity: I don't mind. I often put such annotation even when they are not necessary. I was just curious as to why it made it into this particular diff.

-- 'Control.Monad.Reader.Class.MonadReader' instance.
newtype MonadReader (m :: * -> *) (a :: *) = MonadReader (m a)
deriving (Functor, Applicative, Monad, MonadIO)
instance Reader.MonadReader r m => HasReader tag r (MonadReader m) where
Copy link
Member

Choose a reason for hiding this comment

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

For every tag? Why not I suppose.

-- The constraint raises @-Wsimplifiable-class-constraints@.
-- This could be avoided by instead placing @HasField'@s constraints here.
-- Unfortunately, it uses non-exported symbols from @generic-lens@.
( Generic r', Generic.HasField' field r' r, HasReader tag r' m )
Copy link
Member

Choose a reason for hiding this comment

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

Maybe call the record r, but the type of the field a instead of r', it would be a hint more readable, I think.

Copy link
Member Author

Choose a reason for hiding this comment

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

a is already taken in the instance signatures. Maybe v for value?

src/HasState.hs Outdated
deriving via (TheMonadState (SState.StateT s' (m :: * -> *)))
instance (Has tag s s', Monad m) => HasState tag s (SState.StateT s' m)
stateOnLens :: Lens' s' s -> (s -> (a, s)) -> s' -> (a, s')
stateOnLens l f s' = let (a, s) = f (view l s') in (a, set l s s')
Copy link
Member

Choose a reason for hiding this comment

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

Fun fact: stateOnLens l is l @(a,) .

Ok, it's more than a fun fact: it's a bit more efficient too 🙂

Copy link
Member Author

Choose a reason for hiding this comment

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

Thanks! I thought there must be a better way of doing this. :)

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.

2 participants