3

I read many articles about contramap and find https://hackage.haskell.org/package/contravariant-1.4.1/docs/Data-Functor-Contravariant.html#g:3 is the best.

Anyway I found, how to use for example:

*Lib Data.Functor.Contravariant> a = Predicate (\x -> x > 20)
*Lib Data.Functor.Contravariant> :t contramap
contramap :: Contravariant f => (a -> b) -> f b -> f a
*Lib Data.Functor.Contravariant> :t contramap (\x -> x * 2)
contramap (\x -> x * 2) :: (Num b, Contravariant f) => f b -> f b
*Lib Data.Functor.Contravariant> :t contramap (\x -> x * 2) a
contramap (\x -> x * 2) a :: (Ord b, Num b) => Predicate b
*Lib Data.Functor.Contravariant> x = contramap (\x -> x * 2) a
*Lib Data.Functor.Contravariant> getPredicate x 45
True 

But could not figure out, where it can be useful.

On the website, that I posted above, it says:

Whereas in Haskell, one can think of a Functor as containing or producing values, a contravariant functor is a functor that can be thought of as consuming values.

Look at the definition of the functor:

class Functor (f :: * -> *) where
  fmap :: (a -> b) -> f a -> f b

It consumes the value of type a and it produces the value of type b. On contramap it consumes the value.

class Contravariant (f :: * -> *) where
  contramap :: (a -> b) -> f b -> f a

Which variable type does it consume a or b?

Also a question from https://www.fpcomplete.com/blog/2016/11/covariance-contravariance about POSITIVE AND NEGATIVE POSITION. On the website it says:

  • 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

Looking at contramap type definition:

contramap :: (a -> b) -> f b -> f a

Which type variable does the author point to?

softshipper
  • 32,463
  • 51
  • 192
  • 400
  • You've misunderstood what the quoted portion is saying; but also it isn't really clear language, and you should simply avoid reading any explanation which begins with "one can think of...". That said, what it means is that "a Functor" (a value `x :: F A` for some `F` with `Functor F` and some `A`) can be thought of as a computation producing values of type `A`; and a contravariant functor can be thought of as consuming values of type `A`. – user2407038 Feb 02 '18 at 20:12

3 Answers3

9

A Functor f is thought of to 'contain' or 'produce' values in the sense that f a is like a container of a values. fmap allows you to transform the values held by f.
Examples:

  • [a] 'contains' a non-negative number of values of type a
  • IO a potentially does some IO and 'returns' or 'produces' a value of type a
  • (->) r a 'contains' a value of type a for each value of r

Now a Contravariant f is something that can 'take in' or 'consume' values. contramap allows you to transform the thing f a takes before it consumes them.
The main example for this is usually using something like
newtype Op r a = Op { runOp :: a -> r }
(note the Predicate you used seems to just be Op Bool)
Now we have something that can 'consume' values of type a.(the analogy might make more sense for Op (IO ()))
Continuing this example of 'consumption' of values, consider o = Op (\x -> putStrLn x) :: Op (IO ()) String, now what if we want to make use of o, but for values of type Show a => a? well that's what contramap is for!
contramap show o :: Show a => Op (IO ()) a
(note that in this simple case runOp (contramap show o) is just print)

EDIT:
Another interesting thing to note about Contravariant is with regard to composing it.
Given Contravariant c, Contravariant d, Functor f and
newtype Compose f g a = Compose { runCompose :: f (g a) }
we have:

  • Compose f c is also Contravariant
    contramap f (Compose fca) = Compose $ fmap (contramap f) fca
  • Compose c f is also Contravariant
    contramap f (Compose cfa) = Compose $ contramap (fmap f) cfa
  • Compose c d is actually a Functor
    fmap f (Compose cda) = Compose $ contramap (contramap f) cda
cmaher
  • 5,100
  • 1
  • 22
  • 34
Mor A.
  • 3,805
  • 2
  • 16
  • 19
  • 1
    The definition of the data type has to be like `Op { runOp :: a -> r }` not for example `contramap` on `Maybe a` does not make sense, because it produces a value right? – softshipper Feb 04 '18 at 10:19
  • 1
    `Maybe a` can be thought of as a list of length 0 or 1, that is, it 'contains' 0 or 1 values of type `a`. You cannot define `contramap` for `Maybe` because if you did, you would have `phantom = contramap (const ()) . fmap (const ()) :: (Functor f, Contravariant f) => f a -> f b`, and that doesn't make sense since then we could convert any `a` to any `b` by using `fromJust . phantom . Just`. For an example of a `Functor` that is also `Contravariant`, there's `newtype Const c a = Const { getConst :: c }`, where the `fmap f = contramap f = Const . getConst` – Mor A. Feb 04 '18 at 12:46
5

Look at the definition of the functor:

class Functor (f :: * -> *) where
    fmap :: (a -> b) -> f a -> f b

It consumes the value of type a and it produces the value of type b.

This is not precise, and is the source of your confusion.

In particular, there are three distinct objects in the type of fmap: a function a -> b, a "functorial" value f a, and a "functorial" value f b, and whether a is consumed or produced varies across these objects, as does whether b is consumed or produced.

  • A function of type a -> b consumes and a and produces a b. (This is the sentence you said, but with a new addition specifying on which object it applies.)
  • If f is a Functor, then a value of type f a "produces" as.
  • If f is a Functor, then a value of type f b "produces" bs.

We can extend these observations to larger function types.

  • If f is a Functor, then a function of type f a -> f b consumes f as and produces f bs; and because functors are positive this in turn means that the function consumes as and produces bs.
  • If f is a Functor, then a function of type (a -> b) -> f a -> f b consumes an a -> b -- that is, in the implementation, we will produce as to provide to the a -> b function and consume the bs that it returns! It does this to produce an f a -> f b, which consumes as and produces bs.

N.B. the role of "consume" and "produce" alternates quite a lot in the above description, even within the single object that is called fmap; so saying "fmap consumes as and produces bs" is not telling the whole story.

The following statement is also imprecise in a similar way:

Whereas in Haskell, one can think of a Functor as containing or producing values, a contravariant functor is a functor that can be thought of as consuming values.

You have interpreted this to mean that fmap as a (function) object contains values, while contramap produces values. But this is not what was intended; instead, the statement was intended to be about the (contravariant) functorial values themselves, not the (contra)maps that are applied to them. A more precise statement is this one:

Whereas in Haskell, one can think of a value of type f a (for which f is a Functor) as containing or producing a values, a contravariant functor f gives rise to values of type f a that can be thought of as consuming a values.

Regarding your question about positive and negative positions, you may enjoy some of my previous discussion of this topic here on SO.

Community
  • 1
  • 1
Daniel Wagner
  • 145,880
  • 9
  • 220
  • 380
1

First, look to the signatures, everything is there.

Second, if you want to "feel" the things, consider the functors and try to verbalize what they do.

A functor does something with its contained type. This can be a consumption or a production of things of this type. A List, clearly "produces" instances of the type. A Printer, clearly, "consumes" instances of the type.

When given a function, a producer builds a function which transforms the production "after". From a simple function, you can get a producer for the function domain in applying the function after the production. The signature of fmap is "covariant".

When given a function, a consumer builds a function which transforms the consumption "before". Provided with a transformer function from int to string, a consumer, which already holds a consumer of string, will easily produce a consumer of int: by applying the function before consuming. The signature of contramap is "contravariant".

We can say that the functions "transform", while the functors "consume" or "produce".

wiki1000
  • 439
  • 4
  • 7