Functors
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 Name | Purescript Name | Frequency of Appearance | Usage |
|---|---|---|---|
| Covariant Functor | Functor | Very frequent | Changes the output of a function |
| Contravariant Functor | Contravariant | Infrequent | Changes 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 Functors.md, 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:
| Name | Type Class' function name | Meaning |
|---|---|---|
| Functor | map/ <$>/<#> | Maps 1 type with a Covariant Functor |
| Contravariant | cmap/>$</>#< | Maps 1 type with a Contravariant Functor |
| Bifunctor | bimap |
|
| Profunctor | dimap |
|
| Invariant | imap | Maps 1 type with either/both a Covariant Functor or/and a Contravariant Functor |
See also this Profunctor explanation