Contravariant Functors Are Weird

0

Correct a demonstrate about nomenclature before we commence; I’ll exhaust “functor” to indicate the specific meaning of the conception:

A functor is a mapping between classes

and Functor and Contravariant to specify the typeclass encodings of functors.


Let’s commence up!

Contravariant functors are extraordinary aren’t they? Covariant functors (that are modelled by the Functor typeclass) are rather straight forward but contravariant functors as their name implies appear to be your whole reverse.

Earlier than we get into what a contravariant functor is, it’s worthwhile to explore at the Functor typeclass which we know and adore.

Functor

A Functor is defined as:

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

We usually tag a Functor to be a “container” or a “producer” of some form, where the feature provided to fmap is utilized to the substances that are “contained” or “produced” in some form constructor1 f.

A straight forward example would possibly presumably well be the record ([]) form, that can signify zero or extra values. Given a [a] we can turn it into a [b] when given a feature a -> b.

files [] a = [] | a :  [a]  -- an approximation of the [] files form

instance Functor [] where
  fmap _ [] = []
  fmap f (x: xs) = f x :  fmap f xs

In the example under we convert a [Int] into a [String] given a feature Int -> String:

import Knowledge.Semigroup ((<>))

myInts ::  [Int]
myInts = [1 .. 5]

emptyInts ::  [Int]
emptyInts = []

intToString ::  Int -> String
intToString n = (show n) <> "!"

myStrings ::  [String]
myStrings = fmap intToString myInts -- ["1!","2!","3!","4!","5!"]

myEmptyString ::  []
myEmptyString = fmap intToString emptyInts  -- []

But any other example would the Seemingly files form, that represents a rate which will or would possibly presumably well now now not exist.

files Seemingly a = Nothing | Correct a

instance Functor Seemingly where
  fmap _ Nothing = Nothing
  fmap f (Correct x) = Correct (f x)

In the example under we convert a Seemingly Int into a Seemingly String given a feature Int -> String:

import Knowledge.Semigroup ((<>))

maybeInt ::  Seemingly Int
maybeInt = Correct 10

notInt ::  Seemingly Int
notInt = Nothing

intToString ::  Int -> String
intToString n = (show n) <> "!"

maybeString ::  Seemingly String
maybeString = fmap intToString maybeInt -- Correct "10!"

notString ::  Seemingly String
notString = fmap intToString notInt -- Nothing

The Functor typeclass has regulations, that guarantee Functor cases behave in a predictable way.

Regulations

Identification

Essentially ought to you smash nothing to the associated rate of a Functor, you get the same Functor you started with.

Composition

fmap (f . g) == fmap f . fmap g

If you happen to convert the tip result of a Functor by fmaping with a feature g after which fmaping that result with a subsequent feature f, it’s the same as composing functions g and f (f . g) after which fmaping once.

Functor Laws

Functor Regulations

The Heinous Style of fmap

Now let’s explore at something a tiny bit a whole lot of. Let’s invent an files form to wrap a predicate of some form. A predicate is something that can attach in tips to a Bool:

newtype Predicate a = Predicate { getPredicate ::  a -> Bool }

An example of a Predicate is greaterThanTen:

greaterThanTen ::  Predicate Int
greaterThanTen = Predicate (n -> n > 10)

that checks whether or now now not a quantity is higher than ten.

We can flee with it getPredicate and an Int:

getPredicate greateThanTen 5  -- False
getPredicate greateThanTen 11 -- Dazzling

It would be worthwhile to elaborate a Functor instance for Predicate – snort if we now maintain got a Predicate Int and we are seeking to turn out to be it into a Predicate String once we now maintain got a Int -> String feature. Let’s strive and enforce that:

instance Functor Predicate where
  -- fmap (a -> b) -> Predicate a -> Predicate b
  fmap f (Predicate p) = Predicate (b -> undefined)
  fmap f (Predicate (a -> Bool)) = Predicate (b -> undefined)  -- expanding p
  fmap (a -> b) (Predicate (a -> Bool)) = Predicate (b -> undefined) -- expanding f

Now we’ve flee into a tiny notify:

How will we fabricate (a -> Bool) and (a -> b) to present us a (b -> Bool) ?

We’re given a b but we don’t maintain access to any functions that after all exhaust a b.

The problem is that we can’t. It’s on account of something known as “polarity” of the kind variable a. No Functor instance for you Predicate.

Polarity

Polarity is a technique of representing variance using the utter of form variables. Let’s retract a straightforward feature a -> b as an illustration.

Function Polarity

Characteristic Polarity

If a kind variable is in input utter adore a it’s miles given a detrimental polarity. Whether it’s miles in an output utter adore b then it’s miles given a certain polarity.

These polarities scheme straight to variant forms.

Particular Covariant
Harmful Contravariant
Each and every Invariant

What this means is that Functors (that are after all covariant functors) require a kind constructor in a covariant utter in expose for you to elaborate a Functor instance for that form.

Let’s explore at a kind that we know has a Functor instance adore Seemingly:

Polarity of the Maybe data type

Polarity of the Seemingly files form

We can seek that the kind variable a happens in a covariant (or output) utter all thru the definition of the Correct constructor.

Now let’s explore at the definition of Predicate files form:

Polarity of the Predicate data type

Polarity of the Predicate files form

We can seek that the kind variable a happens in a contravariant (or input) utter. This ability that we can’t invent a (covariant) Functor instance for this knowledge form.

But we are seeking to scheme issues! What will we smash?

Contravariant

Welcome the Contravariant typeclass to the stage! It’s defined as:

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

Snazzy! Contravariant also takes some extra or less form constructor f honest adore Functor nonetheless it has this weirdly named contramap feature rather than fmap.

     fmap ::  (a -> b) -> f a -> f b -- Functor
contramap ::  (a -> b) -> f b -> f a -- Contravariant
                         ^^^

If we learn fmap as:

If you happen to would possibly presumably well maintain an a in some context and a feature that takes that a and converts it to a b, I will give you a context with a b in it.

we can then learn contramap as:

If you happen to would possibly presumably well maintain a context that needs an a and a feature that can convert bs to as, I will give you a context that needs bs.

But that presumably doesn’t invent noteworthy sense. So let’s strive and explore at this when it involves our non-Functor: Predicate. Predicate has a need for an a, which it then uses to yelp if something about that a is Dazzling or False.

Let’s strive and write a Contravariant instance for Predicate provided that we know that the kind a in Predicate happens in an input utter.

instance Contravariant Predicate where
  -- contramp (a -> b) -> f b -> f a
  contramap (a -> b) -> Predicate b -> Predicate a -- substituting for `f` for Predicate
  contramap aToB (Predicate bToBool) = Predicate (a -> undefined)

Given that we now maintain got a feature a -> b and basically a feature of form b -> Bool (wrapped interior a Predicate b), we can if given an a, convert it to a b using aToB after which provide that b to bToBool to present us a Bool.

Right here’s a barely long-fabricate implementation of the Contravariant instance for Predicate:

instance Contravariant Predicate where
  contramap ::  (a -> b) -> Predicate b -> Predicate a
  contramap aToB (Predicate bToBool) =
    Predicate $ a ->
      let b    = aToB a
          bool = bToBool b
      in bool

contramap on Predicate

contramap on Predicate

or extra succinctly:

instance Contravariant Predicate where
  contramap ::  (a -> b) -> Predicate b -> Predicate a
  contramap f (Predicate b) = Predicate $ b . f

We can seek from the definition of Predicate a that each we are doing is working the provided feature f before the feature interior Predicate b. The reason we smash that’s to adapt a original input form to study an present input form to develop some functionality.

If we revisit the (covariant) Functor instance for Seemingly:

instance Functor Seemingly where
  fmap _ Nothing = Nothing
  fmap aToB (Correct a) = Correct (aToB a)

we can seek that the feature aToB is flee after we now maintain got a rate of a. We smash that to turn out to be a result of some form to 1 other form.

fmap on Maybe

fmap on Seemingly

These are the a will must maintain differences between covariant and contravariant functors:

Functor after Convert outcomes
Contravariant before Adapt inputs

Now that we know the a will must maintain distinction between Functor and Contravariant, let’s explore at how we can exhaust contramap with our Predicate class.

Given that we already maintain a Predicate that determines whether or now now not a quantity is higher than ten:

numGreaterThanTen ::  Predicate Int
numGreaterThanTen = Predicate (n -> n > 10)

snort we are seeking to write one other Predicate that verifies that the scale of String is higher than ten characters.

strLengthGreaterThanTen ::  Predicate String
strLengthGreaterThanTen = Predicate (s -> (dimension s) > 10)

Sure, that’s rather contrived but undergo with me. Let’s also snort we now maintain got a Particular person files form and we are seeking to clutch if a person’s name is over ten characters long – if that is so we attach in tips that to be a protracted name.

files Particular person = Particular person { personName ::  String, personAge ::  Int }

personLongName ::  Predicate Particular person
personLongName = Predicate (p -> (dimension . personName $ p) > 10)

And we can flee these Predicates as:

getPredicate numGreaterThanTen 5 -- False
getPredicate numGreaterThanTen 20 -- Dazzling

getPredicate strLengthGreaterThanTen "hello"       -- False
getPredicate strLengthGreaterThanTen "hello world" -- Dazzling

getPredicate personLongName $ Particular person "John" 30        -- False
getPredicate personLongName $ Particular person "Bartholomew" 30 -- Dazzling

And right here is stunning, but there’s some duplication all one of the best way thru each of the Predicates – particularly the part where we study a quantity to ten:

(n -> n > 10)  -- Int
(s -> (dimension s) > 10) -- String
(p -> (dimension . personName $ p) > 10) -- Particular person

It’d be fine if we didn’t must repeat ourselves.

If we explore at the differences between numGreaterThanTen, strLengthGreaterThanTen and personLongName we can seek that one of the best distinction is that one works on an Int and the others work on String and Particular person respectively. strLengthGreaterThanTen and personLongName each convert their input forms to an Int after which smash the same comparability:

Predicate ((n ::  Int) ->
  let num = id n
  in num > 10 -- (1)
) -- numGreaterThanTen


Predicate ((s ::  String) ->
  let num = dimension s
  in num > 10 -- (1)
) -- strLengthGreaterThanTen

Predicate ((p ::  Particular person) ->
  let name = personName p
      num  = dimension name
  in num > 10 -- (1)
) -- personLongName

The above expansion of the functions demonstrates that even supposing the Predicates themselves maintain a whole lot of input forms, at the tip they are all converted to a quantity which is when put next against the volume ten. This is tagged with (1) in the above example.

We would possibly presumably well additionally additionally seek that one of the best adjustments between the Predicates is the conversion from one form to 1 other before working our comparability feature (1). This is our clue that we can exhaust contramap right here to reuse some functionality.

numGreaterThanTen ::  Predicate Int
numGreaterThanTen = Predicate (n -> n > 10)

strLengthGreaterThanTen2 ::  Predicate String
strLengthGreaterThanTen2 = contramap dimension numGreaterThanTen -- convert the String to an Int, then sprint it to numGreaterThanTen

personLongName2 ::  Predicate Particular person
personLongName2 = contramap (dimension . personName) numGreaterThanTen -- convert the Particular person to an Int, then sprint it to numGreaterThanTen

We get the same outcomes as before:

getPredicate strLengthGreaterThanTen2 "hello"       -- False
getPredicate strLengthGreaterThanTen2 "hello world" -- Dazzling

getPredicate personLongName2 $ Particular person "John" 30        -- False
getPredicate personLongName2 $ Particular person "Bartholomew" 30 -- Dazzling

Now we now maintain got rewritten strLengthGreaterThanTen and personLongName when it involves numGreaterThanTen by honest working a feature before it to turn out to be the forms. This is a straight forward example of a Contravariant Functor where we can reuse some present functionality for a given form if we can convert from our other forms to that form thru some mapping feature.

We would possibly presumably well additionally additionally sprint a tiny bit additional and reuse even extra:

personLongName3 ::  Predicate Particular person
personLongName3 = contramap personName strLengthGreaterThanTen -- convert the Particular person to a String, then sprint it to strLengthGreaterThanTen

Regulations

Correct adore Functor has regulations, Contravariant also has regulations. This is awesome – on account of regulations invent our lives less difficult.

Identification

Essentially ought to you smash now now not alternate the associated rate of a Contravariant functor, you get the same Contravariant functor you started with.

Composition

contramap f . contramap g = contramap (g . f)

If you happen to convert the input to some Contravariant functor by contramaping with feature g after which convert its input to 1 other form by contramaping again with a feature f, it’s the same as composing the functions f and g (g . f) after which contramaping once. See the expose of composition is switched as against once we regarded at the Functor regulations.

Contravariant Laws

Contravariant Regulations

Let’s retract Predicate as an illustration and strive out the identity legislation. The Contravariant instance for Predicate is defined as:

 instance Contravariant Predicate where
   contramap ::  (a -> b) -> f b -> f a
   contramap f (Predicate p) = Predicate (p . f)

Given that we now maintain got a Predicate Int:

numGreaterThanTen ::  Predicate Int
numGreaterThanTen = Predicate (n -> n > 10)

The exhaust of contramap id on the above:

-- identity legislation
contramap id numGreaterThanTen == numGreaterThanTen

-- lhs
Predicate (p . f) -- applying contramap
Predicate (p . id) -- expanding f
Predicate (p) -- applying f
Predicate (n -> n > 10) -- expanding p

-- rhs
numGreaterThanTen
Predicate (n -> n > 10) -- expanding numGreaterThanTen

-- equality
lhs                      == rhs
Predicate (n -> n > 10) == Predicate (n -> n > 10)

As soon as extra using Predicate as an illustration, let’s explore the compositional legislation of Contravariant.

Given that we now maintain got the next Predicates:

numGreaterThanTen ::  Predicate Int
numGreaterThanTen = Predicate (n -> n > 10)

dimension ::  [a] -> Int
personName ::  Particular person -> String

The exhaust of numGreaterThanTen, with dimension and personName:

-- composition legislation
contramap personName . contramap dimension $ numGreaterThanTen = contramap (dimension . personName) numGreaterThanTen


-- lhs
contramap personName . contramap dimension $ numGreaterThanTen
contramap personName . contramap dimension $ Predicate (n -> n > 10) -- expanding numGreaterThanTen
contramap personName (Predicate $ str ->
  let num  = dimension str
     bool  = num > 10
  in bool
) -- applying dimension
Predicate $ person ->
  let str = personName person
      num = dimension str
     bool = num > 10
  in bool
) -- applying personName
=> Predicate Particular person

-- rhs
contramap (dimension . personName) numGreaterThanTen
contramap (person ->
    let str = personName person
        num = dimension str
    in num
) numGreaterThanTen -- expanding dimension . personName
Predicate (person ->
   let str  = personName person
       num  = dimension str
       bool = num > 10 -- expanding numGreaterThanTen
   in bool
)
=> Predicate Particular person

-- equality
lhs == rhs

Predicate (person ->
  let str  = personName person
      num  = dimension str
      bool = num > 10
  in bool

) ==
Predicate (person ->
   let str  = personName person
       num  = dimension str
       bool = num > 10
   in bool
)

Combinators

There are some constructed-in combinators that sprint with Contravariant.

Infix contramap

Linked to the contramap feature the next functions will also be dilapidated infix:

-- infixl 4
(>$<)        ::  Contravariant f => (a -> b) -> f b -> f a
-- contramap :: Contravariant f => (a -> b) -> f b -> f a

A straight forward example of it in exhaust:

p5 ::  Predicate Int
p5 = Predicate $ n -> n == 5

pLength5 ::  Predicate [a]
pLength5 = dimension >$< p5

getPredicate pLength5 "hello"
-- True

getPredicate pLength5 "hello world"
-- False

Same as contramap but with the parameters switched:

-- infixl 4
(>$$<)       ::  Contravariant f => f b      -> (a -> b) -> f a
-- contramap :: Contravariant f => (a -> b) -> f b      -> f a

Infix const

These combinators retract in a constant input and thoroughly ignore the input provided when working the Contravariant instance.

-- infixl 4
(>$) ::  b -> f b -> f a

It has a default implementation of:

(>$) ::  b -> f b -> f a
(>$) = contramap . const

Let’s seek how that works:

-- const when given two values returns the first rate ignoring the 2d
const ::  a -> b -> a
const x _ =  x

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

(>$) ::  b -> f b -> f a
(>$)      = contramap . const
(>$) b    = contramap (const b)   -- simplifying with b
(>$) b    = contramap (a -> b)    -- applying `const b`
(>$) b fb = contramap (a -> b) fb -- simplifying with fb
(>$) b fb = fa                    -- simplifying `contramap (a -> b) fb`

A straight forward example of it in exhaust:

p5 ::  Predicate Int
p5 = Predicate $ n -> n == 5

pLength5 ::  Predicate [a]
pLength5 = contramap dimension p5

getPredicate pLength5 "hello"
-- Dazzling

getPredicate pLength5 "hello world"
-- False

pAlwaysFalse ::  Predicate [a]
pAlwaysFalse = 10 >$ p5

getPredicate pAlwaysFalse "hello"
-- False (on account of 10 /= 5)

getPredicate pAlwaysFalse "hello world"
-- False

Same as above but with the parameters switched:

-- infixl 4
($<) ::  Contravariant f => f b -> b -> f a

LogAction

Let’s explore at one other example of Contravariant. Accept as true with you would possibly presumably well well maintain the next files form that encapsulates doing a tiny bit side develop on some polymorphic form a:

newtype LogAction a = LogAction { unlog ::  a -> IO () }

For our functions we can favor that we are going to exhaust this to log some rate either to the console or to a file or one other medium. This case has been tailored from the LogAction class of the CO-LOG logging library. No doubt take a look at out the library for genuine-world uses of Contravariant and pals.

As we can seek the kind variable a happens in input utter so we needs so as to elaborate a Contravariant instance for it:

instance Contravariant LogAction where
  contramap ::  (b -> a) -> LogAction a -> LogAction b
  contramap bToA logActionA = LogAction $ b -> unlog logActionA (bToA b)

There must be no surprises right here; we flee the provided feature bToA on the input before passing it to the log action.

Right here’s a barely simplified implementation of the above:

instance Contravariant LogAction where
  contramap f logActionA = LogAction $ unlog logActionA . f

So how will we exhaust LogAction? Let’s elaborate a pair of implementations:

putStrLog ::  LogAction String
putStrLog = LogAction putStr

putStrLnLog ::  LogAction String
putStrLnLog = LogAction putStrLn

putStrLog and putStrLn are honest wrappers around putStr and putStrLn from corrupt. Each and every log a String to the console, the variation being that putStrLn sends a newline character to the console after each call.

Right here’s how we’d exhaust putStrLnLog:

unlog putStrLnLog "Hello World"
-- Hello World

Be conscious that LogAction needs an a which on this case is a String.

Now on account of we now maintain got the vitality of contravariance, we needs so as to log out other forms if we can convert them to a String.

Listed below are some examples:

-- straight forward feature around contramap for LogAction
putStringlyLnLog ::  (a -> String) -> LogAction a
putStringlyLnLog f = contramap f putStrLnLog

-- Now we can log Ints
putStrLnInt ::  LogAction Int
putStrLnInt = putStringlyLnLog show

files Particular person = Particular person { name ::  String, age ::  Int }

-- personalized String illustration of Particular person
showPerson ::  Particular person -> String
showPerson (Particular person name age) = "Particular person(name:" <> name <> ", age: " <> (show age) <> ")"

-- Now we can log of us
putStrLnPerson ::  LogAction Particular person
putStrLnPerson = putStringlyLnLog showPerson

-- personalized String illustration of Particular individual that only displays age
showPersonAge ::  Particular person -> String
showPersonAge person =  "age: " <> (show $ age person)

-- Extra Particular person LogAction which outputs only age
putStrLnPersonAge ::  LogAction Particular person
putStrLnPersonAge = putStringlyLnLog showPersonAge

Right here’s how we can flee the above:

unlog putStrLnInt 42
-- 42

unlog putStrLnPerson $ Particular person "Neelix" 60
-- Particular person(name:Neelix, age: 60)

unlog putStrLnPersonAge $ Particular person "Tuvok" 240
-- age: 240

We can seek that LogAction for Particular person, needs a Particular person instance as input to develop the log action.

One thing which is presumably now now not glaring is that we would possibly presumably well additionally additionally adapt an input form to itself. It’s now now not essential to repeatedly convert from one form to 1 other.

Listed below are some example functions which we can exhaust with contramap:

hello ::  String -> String
hello = ("Hello" <>)

there ::  String -> String
there = ("there" <>)

doctor ::  String -> String
doctor = ("Doctor" <>)

area ::  String -> String
area = (" " <>)

Right here’s how we fabricate the above functions into a LogAction:

putStrLnGreeting ::  LogAction String
putStrLnGreeting = contramap area . contramap doctor . contramap area . contramap there . contramap area . contramap hello $ putStrLnLog

Whoa! That’s even laborious to learn. What does it smash? Be conscious from the 2d legislation of Contravariant that:

contramap f . contramap g = contramap (g . f)

Given that, we can rewrite our extremely compositional LogAction adore so:

putStrLnGreeting ::  LogAction String
putStrLnGreeting = contramap  (hello . area . there . area . doctor . area) $ putStrLnLog

As a minimal right here is barely of extra readable – but the gargantuan thing is that gleaming the regulations helped us invent our code extra legible. But aloof – what does this smash?

The trick is to undergo in tips that Contravaraint composition works in reverse to fashioned composition:

contramap f . contramap g = contramap (g . f) -- detect the (g . f) rather than (f. g)

This is how putStrLnGreeting is evaluated:

putStrLnGreeting ::  LogAction String
putStrLnGreeting = contramap  (hello . area . there . area . doctor . area) $ putStrLnLog

unlog putStrLnGreeting "Switzer" -- flee the logger with "Switzer" because the input

-- the input is going to battle thru this sequence of functions: 
-- (hello . area . there . area . doctor . area)

-- applying area
" " <> Switzer
-- applying doctor
"Doctor" <> " " <> Switzer
-- applying area
" " <> "Doctor" <> " " <> Switzer
-- applying there
"there" <> " " <> "Doctor" <> " " <> Switzer
-- applying area
" " <> "there" <> " " <> "Doctor" <> " " <> Switzer
-- applying hello
"Hello" <> " " <> "there" <> " " <> "Doctor" <> " " <> Switzer
-- final output: 
-- Hello there Doctor Switzer

Let’s explore at one extra LogAction which would possibly presumably well presumably be attention-grabbing; One where we ignore the input and return some constant output:

override ::  a -> a -> a
override rate = const rate

A we talked about previously, const is defined as a -> b -> a, where it accepts two inputs but returns the associated rate of the first input (ignoring the 2d input).

Right here’s how we exhaust it with LogAction:

qPutStrLn :: LogAction String
qPutStrLn = contramap (override "This is Q!!") putStrLnLog

-- flee it
unlog qPutStrLn "Picard J L"
-- This is Q!!

Now if our reminiscence serves, we needs so as to smash the same with >$:

qPutStrLnOp ::  LogAction String
qPutStrLnOp = "This is Q!!" >$ putStrLnLog

-- flee it
unlog qPutStrLnOp "Sisko B L"
-- This is Q!!

Equality and Ordering

Now let’s explore at two barely of linked concepts: equality and ordering

Equivalence

Let’s imagine that we now maintain got a datatype known as Equivalence that wraps an equality expression:

newtype Equivalence a = Equivalence { getEquivalence ::  a -> a -> Bool }

Given two values of form a the getEquivalence feature will return a Bool indicating in the occasion that they are equal or now now not.

Now we can seek that both a form variables are in input utter. Let’s elaborate a Contravariant instance for it:

instance Contravariant Equivalence where
  contramap ::  (a -> b) -> Equivalence b -> Equivalence a
  contramap aToB (Equivalence eqB1B2) = Equivalence $ a1 a2 ->
    let b1 = aToB a1
        b2 = aToB a2
    in eqB1B2 b1 b2

One thing essential to demonstrate is that the feature we offer to contramap (a -> b) is flee on twice – once on each of the input parameters (b).

Polarity of Equivalence

Polarity of Equivalence

Given an Equivalence for Int:

intEq ::  Equivalence Int
intEq = Equivalence (==)

We can flee it as:

getEquivalence intEq 1 2
-- False

getEquivalence intEq 1 1
-- Dazzling

We can calculate the equivalence of other forms using contramap:

strLengthEq ::  Equivalence String
strLengthEq = contramap dimension intEq

files Particular person = Particular person { name ::  String, age ::  Int }

personAgeEq ::  Equivalence Particular person -- equality by age
personAgeEq = contramap age intEq

personNameLengthEq ::  Equivalence Particular person -- equality by dimension of name
personNameLengthEq = contramap name strLengthEq

Right here’s how we can flee the above:

-- t1 = Particular person "Tuvok1" 240
-- t2 = Particular person "Tuvok2" 340
-- t3 = Particular person "Neelix" 60
-- t4 = Particular person "Janeway" 40

getEquivalence personAgeEq t1 t2
-- False

getEquivalence personAgeEq t1 t1
-- Dazzling

getEquivalence personAgeEq t2 t2
-- Dazzling

getEquivalence personAgeEq t2 t3
-- False

getEquivalence personNameLengthEq t1 t2
-- Dazzling

getEquivalence personNameLengthEq t3 t4
-- False

getEquivalence personNameLengthEq t1 t4
-- False

Comparison

Let’s imagine that we now maintain got a datatype known as Comparison that wraps a comparability expression:

newtype Comparison a = Comparison { getComparison ::  a -> a -> Ordering }

Given two values of form a the getComparison feature will return an Ordering (LT, GT or EQ) with recognize to each other.

Now we can seek that both a form variables are in input utter as before. Let’s elaborate a Contravariant instance for it:

instance Contravariant Comparison where
  contramap ::  (a -> b) -> Comparison b -> Comparison a
  contramap aToB (Comparison cmpB1B2) = Comparison $ a1 a2 ->
    let b1 = aToB a1
        b2 = aToB a2
    in cmpB1B2 b1 b2

Polarity of Comparison

Polarity of Comparison

We can seek that the wrappers for Equivalence and Comparison are practically the same, as are their Contravariant cases.

Given a Comparison for Int as:

intCmp ::  Comparison Int
intCmp = Comparison study

We can flee it as:

getComparison intCmp 1 1
-- EQ

getComparison intCmp 1 2
-- LT

getComparison intCmp 2 1
-- GT

We can now calculate the comparability of other forms using contramap:

strCmp ::  Comparison String
strCmp = contramap dimension intCmp

personAgeCmp ::  Comparison Particular person
personAgeCmp = contramap age intCmp

fstCmp ::  Comparison a -> Comparison (a, b)
fstCmp compA = contramap fst compA

Nothing original right here. Let’s maintain a seek at learn the technique to form numbers. We exhaust the sortBy feature defined in Knowledge.List from the corrupt package:

sortBy ::  (a -> a -> Ordering) -> [a] -> [a]

We can seek from the sortBy feature definition that it would accept the guidelines wrapped in the Comparison files form:

sortBy        ::  (a -> a -> Ordering) -> [a] -> [a]
getComparison ::   a -> a -> Ordering

Sorting numbers with the above feature:

-- unsortedNumbers = [3, 5, 1, 4, 2]

-- ascending form
sortBy (getComparison intCmp) unsortedNumbers
-- [1,2,3,4,5]

-- descending form
sortBy (flip $ getComparison intCmp) unsortedNumbers
-- [5,4,3,2,1]

See how we honest exhaust the flip feature to alternate between ascending and descending form:

flip ::  (a -> b -> c) -> b -> a -> c

flip honest adjustments the expose of input parameters. flip is awesome 🙂 I saw this scheme first dilapidated at Roman Cheplyaka’s weblog.

But right here’s something attention-grabbing: since we know learn the technique to form Ints we also know learn the technique to form of us by age thru personAgeCmp! Let’s seek that in action:

-- unsortedPeople = [Person "Tuvok1" 240, Person "Janeway" 40, Person "Neelix" 60]

-- ascending form
sortBy (getComparison personAgeCmp) unsortedPeople
-- [Person {name = "Janeway", age = 40},Person {name = "Neelix", age = 60},Person {name = "Tuvok1", age = 240}]

-- descending form
sortBy (flip $ getComparison personAgeCmp)
-- [Person {name = "Tuvok1", age = 240},Person {name = "Neelix", age = 60},Person {name = "Janeway", age = 40}]

Characteristic Kinds

A recurring feature will also be even supposing of being defined as:

newtype RegularFunc a b = RegularFunc { getRegular ::  a -> b }

We can elaborate a Functor instance for RegularFunc on account of b is in output utter. But what about a, which is in input utter? More on that under.

Let’s retract what the definition of the Functor form class looks adore:

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

In the above declaration, f is a kind constructor with one form gap. Given RegularFunc which has two form holes (a and b), we now maintain got to absorb one in, in expose to exhaust it with the Functor instance implementation. To complete this we repair a and get the kind constructor RegularFunc a. We can’t repair b as partial utility of forms is performed from left to correct (holes can only be on the supreme).

instance Functor (RegularFunc a) where
  fmap ::  (b -> c) -> f b -> f c
  fmap = (.)

We can’t elaborate a Contravariant instance for a on account of we now maintain got to repair a (we can’t elaborate behaviour over it). All we now maintain got to play with is b which is in output utter (and on account of this truth covariant)

Oh! Device on! If only we didn’t must repair a. What if we would possibly presumably well repair b instead? We don’t care about b. b is tiresome to us.

Let’s dream up this form of kind and get in touch with it Op – for opposite of customary:

newtype Op a b = Op { getOp ::  b -> a }

Now we can seek that the kind b is in input utter all thru the guidelines form. It’s also on the supreme of Op a b meaning we don’t must repair it.

Op a b will also be a tiny bit confusing on account of we now maintain got switched the utter of form parameters a and b as they had been in RegularFunc; a is the output and b is the input.

RegularFunc a b Input Output
Op a b Output Input

And guess what? We can now repair a (which is now our output) and would possibly presumably well elaborate a Contravariant instance for Op:

instance Contravariant (Op a) where
  contramap ::  (c -> b) -> Op a b -> Op a c
  contramap cToB (Op bToA) = Op $ c ->
    let b = cToB c
    in bToA b

Right here’s a straightforward example of learn the technique to exhaust it:

stringsLength ::  Op Int [String]
stringsLength = Op $ sum . fmap dimension

unqiueStringsLength ::  Op Int (S.Plot String)
unqiueStringsLength = contramap S.toList stringsLength

If we know learn the technique to sum all the lengths of a [String] we can adapt that feature to sum the lengths of a Plot of String:

import Knowledge.Plot (fromList)

namesList = ["Paris", "Kim", "B'Elanna", "Seven"]
namesSet  = fromList namesList

getOp stringsLength $ namesList
-- 21

getOp unqiueStringsLength $ namesSet
-- 21

Now Predicate, Comparison, Equivalence and Op seem adore worthwhile files structures. The comely files is that they exist already in the Knowledge.Functor.Contravariant package from corrupt so that you simply don’t must write them yourself.

One attention-grabbing implementation a part of the Comparison and Equivalence Contravariant cases is that they’re utilized using the on feature:

newtype Equivalence a = Equivalence { getEquivalence ::  a -> a -> Bool }

instance Contravariant Equivalence where
  contramap f g = Equivalence $ on (getEquivalence g) f

The on feature is defined as:

on ::  (b -> b -> c) -> (a -> b) -> a -> a -> c
(.*.) `on` f = x y -> f x .*. f y

Essentially given a feature b -> b -> c and a feature a -> b, the 2d feature will be utilized to each input of form a changing it to a b after which the first feature is utilized on the reworked inputs. Such reuse. 🙂

More Polarity

Let’s retract a seek at the CallbackRunner example from FP Entire:

newtype CallbackRunner a =
  CallbackRunner {
    runCallback ::  (a -> IO ()) -> IO ()
  }

Form variable a is in input utter so we needs so as to write a Contravariant instance for it:

instance Contravariant CallbackRunner where
  contramap ::  (a -> b) -> CallbackRunner b -> CallbackRunner a
  contramap aToB (CallbackRunner runCallbackB) = CallbackRunner $ aToIO ->
    runCallbackB $ b ->
      let a = undefined -- where will we get an `a` from?
      in aToIO a

-- if we had a (b -> a) we would possibly presumably well convert the `b` to an `a`

Hmm. Now it looks adore we now maintain got a notify. There doesn’t appear to anyway for us to get an a to sprint to aToIO to complete the implementation. We maintain a b and if there became once a feature b -> a rather than our a -> b, we would possibly presumably well convert that b to an a and it would possibly well actually presumably well well all work.

This is on account of there’s extra to the polarity yarn than I’ve shared up till now. Whereas a is in input utter in a -> IO(), it’s polarity adjustments when it’s also dilapidated as an input to the feature (a -> IO ()) -> IO (). I previously talked about that an input utter is a detrimental polarity and an output utter is a certain polarity.

To determine the closing polarity of something we now maintain got to multiply its polarities at each context it’s miles dilapidated interior in the feature definition. More on this under.

Polarity multiplication is similar to the multiplication of certain and detrimental numbers:

Polarity Multiplication Table

Particular x Particular Particular
Particular x Harmful Harmful
Harmful x Particular Harmful
Harmful x Harmful Particular

Let’s strive and figure out the polarity of a given our original found multiplication skills. Given runCallback:

runCallback ::  (a -> IO ()) -> IO ()

a is in input or detrimental utter in:

but interior complete feature it’s a barely a whole lot of yarn:

(a -> IO ()) -> IO () -- func
x = (a -> IO ())      -- assigning (a -> IO ()) to x in func
x -> IO ()            -- substituting x in func

We can seek that x in the above example is in input or detrimental utter as properly. Given that x is a -> IO ():

(a -> IO ()) -> IO ()
-- a -> IO (a is detrimental)
-- (a -> IO ()) -> IO () (your whole parenthesis are in detrimental utter)
-- polarity of a: detrimental detrimental = certain

Polarity Multiplication

Polarity Multiplication

Given that a is now in output or certain utter, we needs so as to write a Functor instance for it:

instance Functor CallbackRunner where
  fmap ::  (a -> b) -> CallbackRunner a -> CallbackRunner b
  fmap aToB (CallbackRunner runCallbackA) = CallbackRunner $ bToIO ->
    runCallbackA $ a ->
      let b      = aToB a
          result = bToIO b
      in result

And we can!! If you happen to’d possess to dig extra into polarities there are some comely exercises at the FP Entire article.

Invariant Functors

We briefly talked about invariant functors when speaking about Polarity but by no technique talked about them again till now. The Invariant typeclass is the guardian typeclass of both Functor and Contravariant)

Simplified Functor Hierarchy

Simplified Functor Hierarchy

Given that this put up is rather long, I’m only going to claim that Invariant has both covariant and contravariant functions in its definition:

class Invariant f where
  invmap ::  (a -> b) -> (b -> a) -> f a -> f b

where a -> b is the feature to exhaust if f a is a Functor and b -> a is the feature to exhaust if f a is Contravariant.

I would possibly presumably well write one other article about invariant functors if I if truth be told feel the need for it, but for the time being checkout these articles to get you started.

Abstract

Optimistically this has shed some gentle onto contravariant functors and one of the best way they are dilapidated and one of the best way they’ll also be utilized. In a future article I’m hoping to quilt Divisible and Decidable typeclasses that blueprint up from Contravariant.

The offer for this article will also be found on Github.

A tall “Thank You” to George Wilson for interesting me to dig deeper into this subject alongside with his gleaming presentations on Functors.

A tall thanks also to Andrew Newman who reviewed this article.

Articles

Video

Books

Questions and Solutions

Programs

Definitions

Form constructor (1)

An files form that needs one or extra form variables to be thoroughly defined.

Shall we embrace, Seemingly is a kind constructor and Seemingly Int is a kind.

Read More

Leave A Reply

Your email address will not be published.