Skip to content

Non-standalone deriving clause does not pick up superclass quantified coercible constraint

Summary

I have a case where a standalone deriving via typechecks but the non-standalone equivalent does not due to unknown roles, despite the derived class constraining the roles appropriately:

{-# LANGUAGE DerivingVia #-}
{-# LANGUAGE QuantifiedConstraints #-}

module Test where

import Control.Exception
import Data.Coerce
import Control.Monad.Trans.Reader

class (Monad m, forall x y. Coercible x y => Coercible (m x) (m y)) => MonadWith m where
  stateThreadingGeneralWith
    :: GeneralAllocate m SomeException releaseReturn b a
    -> (a -> m b)
    -> m (b, releaseReturn)

{-
newtype FooT m a = FooT (ReaderT Int m a) deriving newtype (Functor, Applicative, Monad) deriving MonadWith via ReaderT Int m

Test.hs:46:99: error:
    • Couldn't match representation of type: m (GeneralAllocated
                                                  (ReaderT Int m) SomeException releaseReturn b a)
                               with that of: m (GeneralAllocated
                                                  (FooT m) SomeException releaseReturn b a)
        arising from the coercion of the method ‘stateThreadingGeneralWith’
          from type ‘forall releaseReturn b a.
                     GeneralAllocate (ReaderT Int m) SomeException releaseReturn b a
                     -> (a -> ReaderT Int m b) -> ReaderT Int m (b, releaseReturn)’
            to type ‘forall releaseReturn b a.
                     GeneralAllocate (FooT m) SomeException releaseReturn b a
                     -> (a -> FooT m b) -> FooT m (b, releaseReturn)’
      NB: We cannot know what roles the parameters to ‘m’ have;
        we must assume that the role is nominal
    • When deriving the instance for (MonadWith (FooT m))
   |
46 | newtype FooT m a = FooT (ReaderT Int m a) deriving newtype (Functor, Applicative, Monad) deriving MonadWith via ReaderT Int m
   |                                                                                                   ^^^^^^^^^

Same issue with:

newtype FooT m a = FooT (ReaderT Int m a) deriving newtype (Functor, Applicative, Monad, MonadWith)
-}
newtype FooT m a = FooT (ReaderT Int m a) deriving newtype (Functor, Applicative, Monad)
deriving via ReaderT Int m instance (MonadWith m) => MonadWith (FooT m)

newtype GeneralAllocate m e releaseReturn releaseArg a
  = GeneralAllocate ((forall x. m x -> m x) -> m (GeneralAllocated m e releaseReturn releaseArg a))
data GeneralAllocated m e releaseReturn releaseArg a = GeneralAllocated
  { allocatedResource :: !a
  , releaseAllocated :: !(Either e releaseArg -> m releaseReturn)
  }

instance (MonadWith m) => MonadWith (ReaderT r m) where
  stateThreadingGeneralWith
    :: forall a b releaseReturn
     . GeneralAllocate (ReaderT r m) SomeException releaseReturn b a
    -> (a -> ReaderT r m b)
    -> ReaderT r m (b, releaseReturn)
  stateThreadingGeneralWith (GeneralAllocate allocA) go = ReaderT $ \r -> do
    let
      allocA' :: (forall x. m x -> m x) -> m (GeneralAllocated m SomeException releaseReturn b a)
      allocA' restore = do
        let
          restore' :: forall x. ReaderT r m x -> ReaderT r m x
          restore' mx = ReaderT $ restore . runReaderT mx
        GeneralAllocated a releaseA <- runReaderT (allocA restore') r
        let
          releaseA' relTy = runReaderT (releaseA relTy) r
        pure $ GeneralAllocated a releaseA'
    stateThreadingGeneralWith (GeneralAllocate allocA') (flip runReaderT r . go)

Expected behavior

Both of these should behave the same, and should both work. I think this should also work with deriving newtype instead of having to specify the via type.

Environment

  • GHC version used: 9.2.4, 9.8.1
Edited by Shea Levy
To upload designs, you'll need to enable LFS and have an admin enable hashed storage. More information