Search code examples
haskellfunctoralgebraic-data-typesbifunctor

On inferring fmap for ADTs


Suppose that two new types are defined like this

type MyProductType a = (FType1 a, FType2 a)

type MyCoproductType a = Either (FType1 a) (FType2 a)

...and that FType1 and Ftype2 are both instances of Functor.

If one now were to declare MyProductType and MyCoproductType as instances of Functor, would the compiler require explicit definitions for their respective fmap's, or can it infer these definitions from the previous ones?

Also, is the answer to this question implementation-dependent, or does it follow from the Haskell spec?


By way of background, this question was motivated by trying to make sense of a remark in something I'm reading. The author first defines

type Writer a = (a, String)

...and later writes (my emphasis)

...the Writer type constructor is functorial in a. We don't even have to implement fmap for it, because it's just a simple product type.

The emphasized text is the remark I'm trying to make sense of. I thought it meant that Haskell could infer fmap's for any ADT based on functorial types, and, in particular, it could infer the fmap for a "simple product type" like Writer, but now I think this interpretation is not right (at least if I'm reading Ørjan Johansen's answer correctly).

As for what the author meant by that sentence, now I really have no clue. Maybe all he meant is that it's not worth the trouble to re-define Writer in such a way that its functoriality can be made explicit, since it's such a "simple ... type". (Grasping at straws here.)


Solution

  • First, you cannot generally define new instances for type synonyms, especially not partially applied ones as you would need in your case. I think you meant to define a newtype or data instead:

    newtype MyProductType a = MP (FType1 a, FType2 a)
    newtype MyCoproductType a = MC (Either (FType1 a) (FType2 a))
    

    Standard Haskell says nothing about deriving Functor automatically at all, that is only possible with GHC's DeriveFunctor extension. (Or sometimes GeneralizedNewtypeDeriving, but that doesn't apply in your examples because you're not using a just as the last argument inside the constructor.)

    So let's try that:

    {-# LANGUAGE DeriveFunctor #-}
    data FType1 a = FType1 a deriving Functor
    data FType2 a = FType2 a deriving Functor
    newtype MyProductType a = MP (FType1 a, FType2 a) deriving Functor
    newtype MyCoproductType a = MC (Either (FType1 a) (FType2 a)) deriving Functor
    

    We get the error message:

    Test.hs:6:76:
        Can't make a derived instance of ‘Functor MyCoproductType’:
          Constructor ‘MC’ must use the type variable only as the last argument of a data type
        In the newtype declaration for ‘MyCoproductType’
    

    It turns out that GHC can derive the first three, but not the last one. I believe the third one only works because tuples are special cased. Either doesn't work though, because GHC doesn't keep any special knowledge about how Either treats its first argument. It's nominally a mathematical functor in that argument, but not a Haskell Functor.

    Note that GHC is smarter about using variables only as last argument of types known to be Functors. The following works fine:

    newtype MyWrappedType a = MW (Either (FType1 Int) (FType2 (Maybe a))) deriving Functor
    

    So to sum up: It depends, GHC has an extension for this but it's not always smart enough to do what you want.