Itamar Ravid
December 13, 2018
580

# A Fistful of Functors

Functors show up everywhere in our day-to-day programming. They're so common, we take them for granted – especially in typed functional programming. Besides being common, they're incredibly useful for code reuse. However, functors have some lesser known variants: profunctors, bifunctors, contravariant functors, and so on. Guess what? They're amazingly useful, especially combined with other abstractions in the functional programming toolkit! In this talk, you'll discover the many species of functors and see how they can help you with tasks such as serialisation, stream processing, and more.

## Itamar Ravid

December 13, 2018

## Transcript

1. A Fistful of Functors
Itamar Ravid
Scala Exchange 2018
Itamar Ravid - @itrvd - #scalaX 1

2. The map function
Everyone loves the map function!
val list: List[Int] = List(1, 2, 3)
val mapped: List[String] =
list.map(_.toString)
Itamar Ravid - @itrvd - #scalaX 2

3. The map function
Everyone loves the map function!
val option: Option[Int] = Some(1)
val mapped: Option[String] =
option.map(_.toString)
Itamar Ravid - @itrvd - #scalaX 3

4. The map function
Everyone loves the map function!
val tuple: (String, Int) = ("something", 1)
val mapped: (String, String) =
tuple.map(_.toString)
Itamar Ravid - @itrvd - #scalaX 4

5. The map function
Everyone loves the map function!
import cats.effect.IO
val io: IO[Int]
val mapped: IO[String] =
io.map(_.toString)
Itamar Ravid - @itrvd - #scalaX 5

6. Derived combinators
def fproduct[A, B](f: A => B): IO[(A, B)]
val result: IO[(Int, String)] =
io.fproduct(_.toString)
Itamar Ravid - @itrvd - #scalaX 6

7. Derived combinators
def as[A, B](b: B): IO[B]
val result: IO[String] =
io.as("Replaced value")
Itamar Ravid - @itrvd - #scalaX 7

8. Derived combinators
def void: IO[Unit]
val result: IO[Unit] =
io.void
Itamar Ravid - @itrvd - #scalaX 8

9. Functor
The map function comes from the Functor typeclass:
trait Functor[F[_]] {
def map[A, B](fa: F[A])(f: A => B): F[B]
}
// Laws:
fa.map(identity) <-> fa
fa.map(f).map(g) <-> fa.map(f andThen g)
Itamar Ravid - @itrvd - #scalaX 9

10. Deceitful functors
The laws help us root out the bad seeds:
new Functor[Option] {
def map[A, B](fa: Option[A])(f: A => B): Option[B] =
None
}
• Composition:
!
• Identity:
"
Itamar Ravid - @itrvd - #scalaX 10

11. The essence of a functor
Are functors just about replacing the value?
def map[A, B](fa: F[A])(f: A => B): F[B]
Itamar Ravid - @itrvd - #scalaX 11

12. The essence of a functor
Are functors just about replacing the value?
def map[A, B](fa: F[A])(f: A => B): F[B]
They are, but there's more!
Itamar Ravid - @itrvd - #scalaX 12

13. The essence of a functor
Are functors just about replacing the value?
def map[A, B](f: A => B): F[A] => F[B]
They are, but there's more!
Itamar Ravid - @itrvd - #scalaX 13

14. The essence of a functor
Are functors just about replacing the value?
def map[A, B](f: A => B): F[A] => F[B]
They are, but there's more!
Give me a plain function, I'll give you a fancy one.
Itamar Ravid - @itrvd - #scalaX 14

15. The essence of a functor
Let's use a Parser as an example. Here's the deﬁnition:
type Parser[A] = String => Option[A]
Itamar Ravid - @itrvd - #scalaX 15

16. The essence of a functor
And here's a Person parser:
case class Person(name: String, age: Int)
val personParser: Parser[Person] = _.split(",") match {
case Array(name, age) => Some(Person(name, age.toInt))
case _ => None
}
Itamar Ravid - @itrvd - #scalaX 16

17. The essence of a functor
val nameParser: Parser[String] = personParser.map(_.name)
map for this personParser means that:
• if you know how to transform persons to names
(Person => String)
Itamar Ravid - @itrvd - #scalaX 17

18. The essence of a functor
val nameParser: Parser[String] = personParser.map(_.name)
map for this personParser means that:
• if you know how to transform persons to names
(Person => String)
• you know, free of charge, to transform person parsers to name
parsers
(Parser[Person] => Parser[String])
Itamar Ravid - @itrvd - #scalaX 18

19. The essence of a functor
trait Functor[F[_]] {
def map[A, B](fa: F[A])(f: A => B): F[B]
}
This is actually a covariant functor.
Itamar Ravid - @itrvd - #scalaX 19

20. Producers of values
Covariant functors produce values.
val parse: Parser[Int] // produces an int
val opt: Option[Int] // might produce an int
val list: List[Int] // produces zero or more ints
Itamar Ravid - @itrvd - #scalaX 20

21. Producers of values
Covariant functors produce values.
val parse: Parser[Int] // produces an int
val opt: Option[Int] // might produce an int
val list: List[Int] // produces zero or more ints
Let's look at the dual of a producer.
Itamar Ravid - @itrvd - #scalaX 21

22. Consumers of values
Here's a predicate function:
type Predicate[A] = A => Boolean
Itamar Ravid - @itrvd - #scalaX 22

23. Consumers of values
And an integer predicate:
val predicate: Predicate[Int] = _ % 2 == 0
Itamar Ravid - @itrvd - #scalaX 23

24. Consumers of values
And an integer predicate:
val predicate: Predicate[Int] = _ % 2 == 0
And an integer rendering function:
val render: Int => String = _.toString
Itamar Ravid - @itrvd - #scalaX 24

25. Consumers of values
And an integer predicate:
val predicate: Predicate[Int] = _ % 2 == 0
And an integer rendering function:
val render: Int => String = _.toString
Can we compose them?
Itamar Ravid - @itrvd - #scalaX 25

26. Consumers of values
Itamar Ravid - @itrvd - #scalaX 26

27. Consumers of values
Itamar Ravid - @itrvd - #scalaX 27

28. Consumers of values
We can try composing with a producer:
Itamar Ravid - @itrvd - #scalaX 28

29. Consumers of values
Which in code is:
val predicate: Predicate[Int] = _ % 2 == 0
val produce: String => Int = _.toInt
val composed: Predicate[String] =
produce andThen pred
Itamar Ravid - @itrvd - #scalaX 29

30. Consumers of values
Interesting!
Predicate[Int] looks like a functor,
produce: String => Int acted as the mapping function,
but the types are backwards.
Itamar Ravid - @itrvd - #scalaX 30

31. Contravariant functors
Contravariant functors are exactly these "backward" functors:
trait Contravariant[F[_]] {
def contramap[A, B](fa: F[A])(f: B => A): F[B]
}
Itamar Ravid - @itrvd - #scalaX 31

32. Contravariant functors
Contravariant functors are exactly these "backward" functors:
trait Contravariant[F[_]] {
def contramap[A, B](fa: F[A])(f: B => A): F[B]
}
Contrast with the covariant functor:
def contramap[A, B](fa: F[A])(f: B => A): F[B]
def map[A, B](fa: F[A])(f: A => B): F[B]
Itamar Ravid - @itrvd - #scalaX 32

33. Contravariant functors
And with the rearranged form:
def contramap[A, B](f: B => A): F[A] => F[B]
def map[A, B](f: A => B): F[A] => F[B]
Itamar Ravid - @itrvd - #scalaX 33

34. Contravariant functors
Can't forget the laws!
trait Contravariant[F[_]] {
def contramap[A, B](fa: F[A])(f: B => A): F[B]
}
fa.contramap(identity) <-> fa
fa.contramap(f).contramap(g) <->
fa.contramap(g andThen f)
Itamar Ravid - @itrvd - #scalaX 34

35. Contravariant functors
So in our case, we could say:
val predicate: Predicate[Int] = _ % 2 == 0
val produce: String => Int = _.toInt
val composed: Predicate[String] =
predicate contramap produce
Itamar Ravid - @itrvd - #scalaX 35

36. Contravariant functors in the wild
Anything that looks like a consuming function:
type Encoder[A] = A => String
type Ordering[A] = (A, A) => ComparisonResult
type LeftFold[S, A] = (S, A) => S
Itamar Ravid - @itrvd - #scalaX 36

37. What's the point?
To adapt a consumer, you just need a plain function!
// ZIO Streams' Sink
trait Sink[E, A0, A, R]
val sum: Sink[Nothing, Nothing, Int, Int] =
Sink.fold(0)((acc, el) => Sink.Step.more(acc + el))
case class Person(name: String, salary: Int)
val salarySum: Sink[Nothing, Nothing, Person, Int] =
sum.contramap(_.salary)
Itamar Ravid - @itrvd - #scalaX 37

38. A conundrum
Is this function a contravariant or covariant functor?
type Semigroup[A] = (A, A) => A
Itamar Ravid - @itrvd - #scalaX 38

39. A conundrum
Is this function a contravariant or covariant functor?
type Semigroup[A] = (A, A) => A
Both!
Itamar Ravid - @itrvd - #scalaX 39

40. Invariant functor
trait InvariantFunctor[F[_]] {
def imap(fa: F[A])(f: A => B)(g: B => A): F[B]
}
// Laws:
fa.imap(identity)(identity) <-> fa
fa.imap(f1)(g1).imap(f2)(g2) <->
fa.imap(f1 andThen f2)(g2 andThen g1)
Itamar Ravid - @itrvd - #scalaX 40

41. Invariant functor
So let's say I have another newtype:
case class Age(age: Int)
And I need a Semigroup for it.
Itamar Ravid - @itrvd - #scalaX 41

42. Invariant functor
Here's our Semigroup[Int]:
Itamar Ravid - @itrvd - #scalaX 42

43. Invariant functor
And we can use imap to get Semigroup[Age]:
Itamar Ravid - @itrvd - #scalaX 43

44. Invariant functor
In code, this looks like:
val intSemigroup: Semigroup[Int] = _ + _
case class Age(age: Int)
val ageSemigroup = intSemigroup.imap(Age(_))(_.age)
Itamar Ravid - @itrvd - #scalaX 44

45. Functor composition
An interesting thing about functors is that they compose:
If F[_] is a Functor,
and G[_] is a Functor,
then F[G[_]] is a Functor too.
Itamar Ravid - @itrvd - #scalaX 45

46. Functor composition
To give a few examples:
IO[Option[Either[String, Person]]]
List[Predicate[String]]
Predicate[List[String]]
Itamar Ravid - @itrvd - #scalaX 46

47. Functor composition
But which functor? These work like + and -:
Itamar Ravid - @itrvd - #scalaX 47

48. Functor composition
What can we do by composing (just) functors?
import cats.data.Nested
val nestedThing: IO[Option[Either[String, Person]]]
val name: IO[Option[Either[String, String]]] =
Nested(Nested(nestedThing))
.map(_.name)
.value
.value
Itamar Ravid - @itrvd - #scalaX 48

49. Functor composition
What can we do by composing (just) functors?
val predicates: List[Predicate[String]]
val personPredicates: List[Predicate[Person]] =
Nested[List, Predicate, String](predicates)
.contramap(_.name)
.value
Itamar Ravid - @itrvd - #scalaX 49

50. Functor composition
What can we do by composing (just) functors?
val stringsPredicate: Predicate[List[String]]
val personsPredicate: Predicate[List[Person]] =
Nested[Predicate, List, String](stringsPredicate)
.contramap(_.name)
.value
Itamar Ravid - @itrvd - #scalaX 50

51. Functor composition
And these are just functors!
Itamar Ravid - @itrvd - #scalaX 51

52. Mapping to the left
Let's recall Either:
sealed abstract class Either[L, R]
case class Right[L, R](r: R) extends Either[L, R]
case class Left[L, R](l: L) extends Either[L, R]
The covariant functor maps the right value.
Itamar Ravid - @itrvd - #scalaX 52

53. Mapping to the left
case class Error(msg: String)
case class CompositeError(errors: Error*)
// have:
val result: Either[Error, Result]
// want:
val result: Either[CompositeError, Result]
Itamar Ravid - @itrvd - #scalaX 53

54. Mapping to the left
We can map the left side using leftMap:
val lmapped: Either[CompositeError, Result] =
result.leftMap(CompositeError(_))
So Either behaves covariantly in L and R. But we need to
distinguish between them.
Itamar Ravid - @itrvd - #scalaX 54

55. Bifunctor
Covariant functors with two slots are called Bifunctors:
trait Bifunctor[F[_, _]] {
def bimap[A, B, C, D](fa: F[A, B])(f: A => C, g: B => D): F[C, D]
}
// Laws:
fa.bimap(identity, identity) <-> fa
fa.bimap(f1, g1).bimap(f2, g2) <->
fa.bimap(f1 andThen f2, g1 andThen g2)
Itamar Ravid - @itrvd - #scalaX 55

56. Bifunctor
And Either has an instance of Bifunctor:
def bimap[A, B, C, D](fa: Either[A, B])(f: A => C, g: B => D): Either[C, D] =
fa match {
case Left(l) => Left(f(l))
case Right(r) => Right(g(r))
}
Itamar Ravid - @itrvd - #scalaX 56

57. Bifunctors in the wild
Apart from Either, the plain Tuple also forms a Bifunctor:
def bimap[A, B, C, D](fa: (A, B))(f: A => C, g: B => D): (C, D) =
(f(fa._1), g(fa._2))
But why should you care?
Itamar Ravid - @itrvd - #scalaX 57

58. Bifunctor composition
Bifunctors can also compose!
type TwoOptions[A, B] = (Option[A], Option[B])
This is a Bifunctor composed on a Functor.
Itamar Ravid - @itrvd - #scalaX 58

59. Bifunctor composition
Bifunctors can also compose!
type ListOfTuples[A, B] = List[(A, B)]
And this is a Functor composed on a Bifunctor.
Itamar Ravid - @itrvd - #scalaX 59

60. Bifunctor composition
Bifunctors can also compose!
type TwoOptions[A, B] = (Option[A], Option[B])
type ListOfTuples[A, B] = List[(A, B)]
They both form a Bifunctor.
Itamar Ravid - @itrvd - #scalaX 60

61. Bifunctor composition
Bifunctors can also compose!
type TwoOptions[A, B] = (Option[A], Option[B])
type ListOfTuples[A, B] = List[(A, B)]
They both form a Bifunctor.
Kmett calls them Biff (for Bifunctor-functor-functor),
and Tannen in Bifunctors.
Itamar Ravid - @itrvd - #scalaX 61

62. Bifunctor composition
With Binested in cats, we can compose a functor on a
bifunctor:
val l: List[(Person, Age)]
val bimapped: List[(String, Int)] =
Binested(l).bimap(_.name, _.value).value
Itamar Ravid - @itrvd - #scalaX 62

63. Bifunctor composition
With Bitraversable, you get more fancy tricks:
val twoOpts: (Option[String], Option[Int])
val flipped: Option[(Int, String)] = twoOpts.bisequence
Itamar Ravid - @itrvd - #scalaX 63

64. Functions
Let's look at this fancy type:
type Func[A, B] = A => B
Would you say this is a Bifunctor?
Itamar Ravid - @itrvd - #scalaX 64

65. Functions
Let's look at this fancy type:
type Func[A, B] = A => B
It has two slots like a Bifunctor,
but we've seen that functions are contravariant in the input.
Itamar Ravid - @itrvd - #scalaX 65

66. Profunctor
A Profunctor is a type with two slots - contravariant and covariant:
trait Profunctor[F[_, _]] {
def dimap[A, B, C, D](fa: F[A, B])(f: C => A, g: B => D): F[C, D]
}
Itamar Ravid - @itrvd - #scalaX 66

67. Profunctor
A Profunctor is a type with two slots - contravariant and covariant:
trait Profunctor[F[_, _]] {
def dimap[A, B, C, D](fa: F[A, B])(f: C => A, g: B => D): F[C, D]
}
Constrast this with the Bifunctor signature:
def dimap[A, B, C, D](fa: F[A, B])(f: C => A, g: B => D): F[C, D]
def bimap[A, B, C, D](fa: F[A, B])(f: A => C, g: B => D): F[C, D]
Itamar Ravid - @itrvd - #scalaX 67

68. Profunctor
And the laws:
trait Profunctor[F[_, _]] {
def dimap[A, B, C, D](fa: F[A, B])(f: C => A, g: B => D): F[C, D]
}
// Laws
fa.dimap(identity, identity) <-> fa
fa.dimap(f1, g1).dimap(f2, g2) <->
fa.dimap(f2 andThen f1, g1 andThen g2)
Itamar Ravid - @itrvd - #scalaX 68

69. Profunctor
You could say that a Profunctor is a generalized function:
Itamar Ravid - @itrvd - #scalaX 69

70. Profunctor
You could say that a Profunctor is a generalized function:
Itamar Ravid - @itrvd - #scalaX 70

71. Profunctors in the wild
The plain function is a Profunctor. Anything cooler?
Itamar Ravid - @itrvd - #scalaX 71

72. Profunctors in the wild
Of course! ZIO Streams' Sink is a profunctor:
trait Sink[E, A0, A, R]
The profunctor is constructed on the A and R parameters.
Itamar Ravid - @itrvd - #scalaX 72

73. Profunctors in the wild
To see this in action, let's go back to our summing sink:
val sum: Sink[Nothing, Nothing, Int, Int] =
Sink.fold(0)((acc, el) => Sink.Step.more(acc + el))
Itamar Ravid - @itrvd - #scalaX 73

74. Profunctors in the wild
To see this in action, let's go back to our summing sink:
val sum: Sink[Nothing, Nothing, Int, Int] =
Sink.fold(0)((acc, el) => Sink.Step.more(acc + el))
We can adapt it to sum salaries and format the result:
val salaries: Sink[Nothing, Nothing, Person, String] =
sum.dimap(_.salary, res => s"The result is \$res")
Itamar Ravid - @itrvd - #scalaX 74

75. Profunctor composition
Unsurprisingly, Profunctors also compose.
We can compose another Functor in one or both slots:
type Output[A, B] = A => List[B]
Itamar Ravid - @itrvd - #scalaX 75

76. Profunctor composition
Unsurprisingly, Profunctors also compose.
We can compose another Functor in one or both slots:
type Output[A, B] = A => List[B]
(yes, this is a Kleisli arrow!)
Itamar Ravid - @itrvd - #scalaX 76

77. Profunctor composition
Unsurprisingly, Profunctors also compose.
We can compose another Functor in one or both slots:
type Input[A, B] = Option[A] => B
Itamar Ravid - @itrvd - #scalaX 77

78. Profunctor composition
Unsurprisingly, Profunctors also compose.
We can compose another Functor in one or both slots:
type Both[A, B] = Option[A] => List[B]
The Profunctor instance let's us "forget" about the
input and output effects.
Itamar Ravid - @itrvd - #scalaX 78

79. Summary
Itamar Ravid - @itrvd - #scalaX 79

80. Summary
Source: https://github.com/tpolecat/cats-infographic by Rob Norris 80

81. Thank you!
Questions?
The video will be available soon here!
Itamar Ravid - @itrvd - #scalaX 81