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

Add a MonadBaseControl instance for CofreeT #193

Open
treeowl opened this issue Sep 25, 2019 · 6 comments
Open

Add a MonadBaseControl instance for CofreeT #193

treeowl opened this issue Sep 25, 2019 · 6 comments

Comments

@treeowl
Copy link

treeowl commented Sep 25, 2019

The following certainly typechecks:

instance (Alternative f, MonadBaseControl b m)
         => MonadBaseControl b (CofreeT f m) where
  type StM (CofreeT f m) a = StM m (CofreeF f a (CofreeT f m a))
  liftBaseWith f = CofreeT $ liftBaseWith $
                              \g -> (:< empty) <$> f (g . runCofreeT)
  restoreM q = CofreeT $ restoreM q

I believe it obeys the MonadTransControl laws under the assumption using the fact that

forall f q.  f <$> liftBaseWith q = liftBaseWith $ \rib -> fmap f (q rib)

While this strikes me as reasonable, I don't have enough experience with the class to know for sure. See basvandijk/monad-control#48.

@treeowl
Copy link
Author

treeowl commented Sep 25, 2019

liftBaseWith laws

liftBaseWith $ const $ return x
= CofreeT $ liftBaseWith $ \g -> (:< empty) <$> const (return x) (g . runCofreeT)
= CofreeT $ liftBaseWith $ \g -> (:< empty) <$> return x
= CofreeT $ liftBaseWith $ \g -> return (x :< empty)
= CofreeT $ liftBaseWith $ const $ return (x :< empty)
= CofreeT $ return (x :< empty)
= return x

This one can surely be compressed some:

liftBaseWith (const (m >>= f))
= CofreeT $ liftBaseWith $ \g -> (:< empty) <$> const (m >>= f) (...)
= CofreeT $ liftBaseWith $ const $ (:< empty) <$> m >>= f
= CofreeT $ liftBaseWith $ const $ m >>= \x -> f x >>= pure . (:< empty)
= CofreeT $ liftBaseWith $ const ((m >>= f) >>= pure . (:< empty))
= CofreeT $ liftBaseWith (const (m >>= f)) >>= liftBaseWith . const . pure . (:< empty)
= CofreeT $ liftBaseWith (const (m >>= f)) >>= pure . (:< empty)
= CofreeT $ (liftBaseWith (const m) >>= liftBaseWith . const . f) >>= pure . (:< empty)

= CofreeT $ liftBaseWith (const m) >>=
                          \a -> (liftBaseWith . const . f) a >>=
                                      \b -> pure (b :< empty)

= CofreeT $ do
    a <- liftBaseWith (const m)
    b <- liftBaseWith (const (f a))
    return $ b :< empty

= CofreeT $ do
    a <- liftBaseWith (const m)
    b <- liftBaseWith (const (f a))
    return $ b :< (empty <|> fmap (>>= liftBaseWith . const . f) empty)

= CofreeT $ do
    a :< m' <- (:< empty) <$> liftBaseWith (const m)
    b :< n <- (:< empty) <$> liftBaseWith (const (f a))
    return $ b :< (n <|> fmap (>>= liftBaseWith . const . f) m')

= CofreeT $ do
    a :< m' <- liftBaseWith (const m) >>= pure . (:< empty)
    b :< n <- liftBaseWith (const (f a)) >>= pure . (:< empty)
    return $ b :< (n <|> fmap (>>= liftBaseWith . const . f) m')

= CofreeT $ do
    a :< m' <- liftBaseWith (const m) >>= liftBaseWith . const . pure . (:< empty)
    b :< n <- liftBaseWith (const (f a)) >>= liftBaseWith . const . pure . (:< empty)
    return $ b :< (n <|> fmap (>>= liftBaseWith . const . f) m')

= CofreeT $ do
    a :< m' <- liftBaseWith $ const $ m >>= pure . (:< empty)
    b :< n <- liftBaseWith $ const $ f a >>= pure . (:< empty)
    return $ b :< (n <|> fmap (>>= liftBaseWith . const . f) m')

= CofreeT $ do
    a :< m' <- liftBaseWith $ \_ -> (:< empty) <$> m
    b :< n <- liftBaseWith $ \_ -> (:< empty) <$> f a
    return $ b :< (n <|> fmap (>>= liftBaseWith . const . f) m')

= (CofreeT $ liftBaseWith $ \_ -> (:< empty) <$> m)
    >>= \x -> CofreeT $ liftBaseWith $ \_ -> (:< empty) <$> f x
= liftBaseWith (const m) >>= \x -> liftBaseWith $ const (f x)

restoreM law

liftBaseWith (\runInBase -> runInBase m) >>= restoreM
= (CofreeT $ liftBaseWith $ \g -> (:< empty) <$> g (runCofreeT m))
     >>= CofreeT . restoreM
= CofreeT $ do
    a :< m' <- liftBaseWith $ \g -> (:< empty) <$> g (runCofreeT m)
    b :< n <- restoreM a
    return $ b :< (n <|> fmap (>>= CofreeT . restoreM) m')

-- Invoking my assumption

= CofreeT $ do
    a :< m' <- (:< empty) <$> liftBaseWith $ \rib -> rib $ runCofreeT m
    b :< n <- restoreM a
    return $ b :< (n <|> fmap (>>= CofreeT . restoreM) m')
= CofreeT $ do
    a <- liftBaseWith $ \rib -> rib $ runCofreeT m
    b :< n <- restoreM a
    return $ b :< n
= CofreeT $ do
    b :< n <- runCofreeT m
    return $ b :< n
= m

@treeowl
Copy link
Author

treeowl commented Sep 26, 2019

The extra law is a free theorem, as Li-Yao Xia explains. So we can do this if it makes some kind of sense.

@treeowl
Copy link
Author

treeowl commented Dec 29, 2020

@RyanGlScott, what do you think?

@RyanGlScott
Copy link
Collaborator

To be honest, I haven't really used monad-control much, so I don't have a strong opinion on it. Since @ekmett has expressed reservations about adding instances from monad-control in the past (see here), I'd like to hear his opinion before going forth and adding a monad-control dependency.

@treeowl
Copy link
Author

treeowl commented Dec 29, 2020 via email

@fumieval
Copy link
Collaborator

I personally think we should avoid MonadBaseControl as much as possible. It makes forking and exception handling of stateful monad transformers "work", but often in undesirable ways. Maybe it makes sense if monad-control didn't have the footgun instances for StateT, ExceptT and so on, but it does so it's hard to say for me that the proposed instance makes sense.

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

No branches or pull requests

3 participants