Itamar Ravid
December 13, 2018
600

# 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 We also get some cool derived functions for

free: 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 We also get some cool derived functions for

free: def as[A, B](b: B): IO[B] val result: IO[String] = io.as("Replaced value") Itamar Ravid - @itrvd - #scalaX 7
8. ### Derived combinators We also get some cool derived functions for

free: 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: val badOptionFunctor = 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

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

#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. What about the left 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

81. ### Thank you! Questions? The video will be available soon here!

Itamar Ravid - @itrvd - #scalaX 81