Variance of Functors

This is a summary of this post and this post.

When we look a Function's definition, we see that it is higher-kinded by two, that is, two types need to be defined before we can have a concrete type:

foreign import data Function :: Type -> Type -> Type

infix ? Function as ->

-- Rather than writing `Function input output`
-- we write `input -> output`
no_sugar :: Function Input Output

syntax_sugar :: Input -> Output

generic_on_input :: forall a. a -> Output

generic_on_output :: forall b. Input -> b

generic_on_both :: forall a b. a -> b

A type can be a Functor if it is higher-kinded by one. In the below example, we specify its input type and leave the output type to be defined in map:

                   -- (input ->)
instance Functor (Function input) where
                              -- (input -> a)     -> (input -> b)
  map :: forall a b. (a -> b) -> Function input a -> Function input b
  map aToB inputToA = (\input -> aToB (inputToA input))

However, what if we specified the output type of Function in the instance head and left the input type to be defined in map? If so, it would look like this:

type FlippedFunc output input = (input -> output)
                   -- (-> output)
instance Functor (FlippedFunc output) where
                              -- (a -> output)        -> (b -> output)
  map :: forall a b. (a -> b) -> FlippedFunc output a -> FlippedFunc output b
  map inputA_To_InputB inputA_To_Output = -- this isn't possible to implement!

We cannot define an instance of Functor in this way because the first argument in map changes an a value to a b value. However, if we flipped the direction of the arrow, we could write the function's body:

type FlippedFunc output input = (input -> output)

                         -- (-> output)
instance SomethingElse (FlippedFunc output) where
                                  -- (a -> output)        -> (b -> output)
  map_ish :: forall a b. (b -> a) -> FlippedFunc output a -> FlippedFunc output b
  map_ish inputB_To_InputA inputA_To_Output =
    (\inputB -> inputA_To_Output (inputB_To_InputA inputB))

The above type class is called Contravariant and map_ish is called cmap.

Functor Re-examined

The above two type classes, Functor and Contravariant, are the same except for the direction of the arrow in the map/cmap's first function argument. The former is called Functor instead of Covariant because it appears more often than the latter.

Real NamePurescript NameFrequency of AppearanceUsage
Covariant FunctorFunctorVery frequentChanges the output of a function
Contravariant FunctorContravariantInfrequentChanges the input of a function

Positive and Negative Position

However, there are actually special names for the input and output types:

Positive position: the type variable is the result/output/range/codomain of the function Negative position: the type variable is the argument/input/domain of the function

Or to put it into meta-language: negativePosition -> positivePosition

These terms are used so that one can ultimately determine whether a given type is a Covariant or Contravariant Functor by the rules of multplication:

| position 1 | position 2 | end position | + | + | + | | - | - | + | | - | + | - | | + | - | - |

To understand how this works in practice, see Contravariant Functors are Weird

The 2+ Different Functors

As explained in ../Variance of, there are two different kinds of Functors: covariant Functors and contravariant Functors.

However, one also hears about Bifunctor, Profunctor, and Invariant. These are just different ways of combining those different kinds of functors together:

NameType Class' function nameMeaning
Functormap/ <$>/<#>Maps 1 type with a Covariant Functor
Contravariantcmap/>$</>#<Maps 1 type with a Contravariant Functor
  • 1st Type: Covariant map (e.g. map)
  • 2nd Type: Covariant map (e.g. map)
  • 1st Type: Contravariant map (e.g. cmap)
  • 2nd Type: Covariant map (e.g. map)
InvariantimapMaps 1 type with either/both a Covariant Functor or/and a Contravariant Functor

See also this Profunctor explanation