4

It's easy to describe adjunction in Haskell:

class Functor f where
  map :: (a -> b) -> f a -> f b

class Functor m => Monad m where
  return :: a -> m a
  join :: m (m a) -> m a

class Functor w => Comonad w where
  extract :: w a -> a
  duplicate :: w a -> w (w a)

class (Comonad w, Monad m) => Adjoint w m | w -> m, m -> w where
  unit :: a -> m (w a)
  counit :: w (m a) -> a

However, I struggle to show that StoreT s w and StateT s m are adjoint.

instance Adjoint w m => Adjoint (StoreT s w) (StateT s m) where
  -- ...

GHC complains that m cannot be determined by StoreT s w because m doesn't appear in StoreT s w:

• Illegal instance declaration for
    ‘Adjoint (StoreT s w) (StateT s m)’
    The coverage condition fails in class ‘Adjoint’
      for functional dependency: ‘w -> m’
    Reason: lhs type ‘StoreT s w’
      does not determine rhs type ‘StateT s m’
    Un-determined variable: m
    Using UndecidableInstances might help
• In the instance declaration for
    ‘Adjoint (StoreT s w) (StateT s m)’

I don't really understand why this is a problem since w -> m by Adjoint w m. GHC further suggests turning on -XUndecidableInstances. Would it be safe to do this in this case? Am I trying to do something impossible?

duplode
  • 33,731
  • 7
  • 79
  • 150
Marvin
  • 1,832
  • 2
  • 13
  • 22
  • 2
    I have added the full error message, as the mention of the coverage condition clarifies what is going on. If that message is somehow different from what you got, please let me know and/or edit it. – duplode May 23 '20 at 16:47
  • Thanks, it matches with the error message I get. – Marvin May 23 '20 at 18:32

1 Answers1

3

Your instance is rejected in the manner you describe because it doesn't follow the coverage condition for the functional dependencies of the class. As discussed in the answers to Why does this instance fail the coverage condition?, the Adjoint w m constraint on your instance is not taken into account when checking the coverage condition.

GHC further suggests turning on -XUndecidableInstances. Would it be safe to do this in this case?

UndecidableInstances is a relatively harmless extension. The worst thing that can happen when you turn it on is a build failure because some instance makes the typechecker loop. While GHC defaults to being conservative about this matter, sometimes you just know better than the compiler and can tell for sure that instance resolution will terminate. In such cases, it is fine to turn UndecidableInstances on. In particular, it seems fine to do it here.


Side note: a problem I do see in your code is that StoreT s w and StateT s m aren't actually adjoints. In particular, a right adjoint must be representable, and StateT s m isn't representable. Even if we manage to write implementations of unit and counit that typecheck, they won't actually give rise to an adjunction isomorphism. See also how Hask/Hask adjunctions are formulated in Data.Functor.Adjunction.

duplode
  • 33,731
  • 7
  • 79
  • 150
  • Thank you for the explanation. I'm relatively new to the whole topic and was hoping to get around those details in particular, guess I'll have to wait a little bit :) – Marvin May 23 '20 at 18:38