Skip to content

DeriveFunctor: Use `contramap' in contravariant position

This rule for cofmap in GHC.Tc.Deriv.Functor can not deal with a type variable in contravariant position

  $(cofmap 'a 'b x)          = x     -- when b does not contain a
  $(cofmap 'a 'a x)          = error "type variable in contravariant position"
  $(cofmap 'a '(b1,b2) x)    = case x of (x1,x2) -> ($(cofmap 'a 'b1) x1, $(cofmap 'a 'b2) x2)
  $(cofmap 'a '(T b1 a) x)   = error "type variable in contravariant position" -- when a only occurs directly as the last argument of T
  $(cofmap 'a '(T b1 b2) x)  = fmap (\y. $(cofmap 'a 'b2 y)) x -- when a only occurs in the last parameter, b2
  $(cofmap 'a '(tb -> tc) x) = \(y:tb[b/a]) -> $(cofmap 'a' 'tc' (x $(fmap 'a 'tb y)))

but now that we have Data.Functor.Contravariant in base why not generate an invocation to contramap?

  $(cofmap 'a '(T b1 a) x) = contramap f x

As an example, this works becuase of the special case for functions

> :set -XDerivingStrategies -XDeriveFunctor
> newtype F a = MkF ((a -> Int) -> Int) deriving stock Functor

but if we use Op Int a (which is a newtype wrapping a -> Int) ghc does not know how to derive it.

> newtype F a = MkF (Op Int a -> Int) deriving stock Functor

<interactive>:10:52: error:
    • Can't make a derived instance of
        ‘Functor F’ with the stock strategy:
        Constructor ‘MkF’ must not use the type variable in a function argument
    • In the newtype declaration for ‘F’

I claim it should work by inserting a contramap invocation there so both these definitions

newtype F a = MkF (Op Int      a -> Int) deriving stock Functor
newtype F a = MkF (Equivalence a -> Int) deriving stock Functor

would generate the following (basically)

instance Functor F where
  fmap :: (a -> a') -> (F a -> F a')
  fmap f (MkF as) = MkF (fmap as (contramap f))
Edited by Icelandjack
To upload designs, you'll need to enable LFS and have an admin enable hashed storage. More information