9. Predicate - checks property of type A
case class Predicate[A](fun: A => Boolean)
9
10. Predicate - complicated one ;)
case class Predicate[A](fun: A => Boolean)
val hasEnoughLength: Predicate[Int] = Predicate(a => a > 3)
10
11. Can we use length to create Predicate[String]?
case class Predicate[A](fun: A => Boolean)
val hasEnoughLength: Predicate[Int] = Predicate(a => a > 3)
def len(s: String): Int = s.length
val isValidCode: Predicate[String] = ???
11
12. Predicate - can we map (define Functor)?
case class Predicate[A](fun: A => Boolean)
import cats.Functor
val predicateFuncor: Functor[Predicate] = new Functor[Predicate] {
def map[A, B](fa: Predicate[A])(f: A => B): Predicate[B] = ???
}
12
13. Predicate - but we can prepend!
case class Predicate[A](fun: A => Boolean)
val hasEnoughLength: Predicate[Int] = Predicate(a => a > 3)
def len(s: String): Int = s.length
val isValidCode: Predicate[String] =
Predicate[String](len andThen hasEnoughLength.fun)
13
14. case class Predicate[A](fun: A => Boolean)
def contramap[A, B](pred: Predicate[A])(fba: B => A): Predicate[B] =
Predicate[B](fba andThen pred.fun)
Predicate - generalize prepend
14
16. Example2 - Can we reuse existing Show?
trait Show[A] {
def show(a: A): String
}
case class Processor(name: String)
case class Multicore(name: String, coreCount: Int)
val sp: Show[Processor] = new Show[Processor] {
override def show(a: Processor): String = s"Processor ${a.name}"
}
def asProcessor(mc: Multicore): Processor = Processor(s"${mc.coreCount} x ${mc.name}")
16
17. Example2 - Can we define Functor for Show?
trait Show[A] {
def show(a: A): String
}
val showFunctor: Functor[Show] = new Functor[Show] {
override def map[A, B](fa: Show[A])(f: A => B): Show[B] = ???
}
17
18. Example2 - no! but we can prepend
trait Show[A] {
def show(a: A): String
}
def contramap[A, B](fa: Show[A])(f: B => A): Show[B] = new Show[B] {
override def show(a: B): String = fa.show(f(a))
}
18
19. Common pattern - prepend to input 1/3
trait Show[A] {
def show(a: A): String
}
def prepend[A, B](fa: Show[A])(f: B => A): Show[B] = new Show[B] {
override def show(a: B): String = fa.show(f(a))
}
19
20. Common pattern - prepend to input 2/3
case class Predicate[A](fun: A => Boolean)
def prepend[A, B](pred: Predicate[A])(fba: B => A): Predicate[B] =
Predicate[B](fba andThen pred.fun)
20
21. Common pattern - prepend to input Contravariant
trait InputPrepender[F[_]] {
def prepend[A, B] (fa: F[A])(f: B => A): F[B]
}
21
22. Mathematicians and names :)
trait Contravariant[F[_]] {
def contramap[A, B] (fa: F[A])(f: B => A): F[B]
}
22
23. Contravariant - like a Functor but flip!
trait Functor[F[_]] {
def map[A, B] (fa: F[A]) (f: A => B): F[B]
}
trait Contravariant[F[_]] {
def contramap[A, B] (fa: F[A]) (f: B => A): F[B]
}
23
24. Contravariant Functor - input + ability to prepend
Functor is “full of” A’s (container A, function producing A)
F[A] we can map A => B and we get F[B]
F[A] we can contramap B => A and we get F[B]
Contravariant “needs” A (index of container, function
consuming A)
24
26. Exercises - Contravariant for scala.math.Equiv
trait Equiv[T] {
def equiv(x: T, y: T): Boolean
}
val EquivContra: Contravariant[Equiv] = new Contravariant[Equiv] {
override def contramap[A, B](fa: Equiv[A])(f: B => A): Equiv[B] = ???
}
Much more:
https://github.com/lemastero/contravariant_profunctor_exercises/blob/master/src/main/scala/contra_pro/Exercise1_Contravariant.
scala
26
27. Contravariant - example Function1
def function1Contravariant[R]: Contravariant[? => R] =
new Contravariant[? => R] {
def contramap[A, B](r: A => R)(f: B => A) = r compose f
}
27
28. Contravariant - example Function1
def function1Contravariant[R]: Contravariant[? => R] =
new Contravariant[? => R] {
def contramap[A, B](r: A => R)(f: B => A) = r compose f
}
Glorified compose of Function1 !!!
28
30. Contravariant - example Reader - Functor
case class Reader[C, V](run: C => V)
def readerFunctor[C] = new Functor[Reader[C,?]] {
def map[A, B](x: Reader[C, A])(f: A => B): Reader[C, B] =
Reader(x.run andThen f)
}
30
31. Contravariant - example Reader - Monad
case class Reader[C, V](run: C => V)
def readerMonad[C] = new Monad[Reader[C, ?]] {
def map[A, B](x: Reader[C, A])(f: A => B): Reader[C, B] =
Reader(x.run andThen f)
def pure[A](a: A): Reader[C, A] =
new Reader(_ => a)
def flatMap[A, B](ma: Reader[C, A])(f: A => Reader[C, B]) = ???
}
31
32. Contravariant - example Reader - Contravariant
case class Reader[C, V](run: C => V)
def readerContra[V] = new Contravariant[Reader[?, V]] {
def contramap[A, B](fa: Reader[A, V])(f: B => A):
Reader[B, V] = Reader(f andThen fa.run)
}
32
33. Functor laws
fmap id = id
fmap f . fmap g = fmap (f . g)
Contravariant laws
contramap id = id
contramap f . contramap g = contramap (g . f)
Contravariant - laws in Haskell
33
36. Contravariant - FunctionN parameters all but last
trait Function2[-T1, -T2, +R]{
def apply(v1: T1, v2: T2): R
}
trait Function3[-T1, -T2, -T3, +R]{
def apply(v1: T1, v2: T2, v3: T3): R
}
//...
All parameters are in negative position, co we could define Contravariant
instance for it.
(Or even BiContravariant, TriContravariant, ... if they existed :)
36
37. Contravariant * Contravariant = Covariant
Compose contravariant functors:
https://github.com/typelevel/cats/blob/master/core/src/main/scala/cats/Contravariant.scala#L11-L15
Input of your input is not my input
But it’s input is ...
https://github.com/lemastero/scala_typeclassopedia/blob/master/src/main/scala/contravariant/HoFStructure.scala
37
39. Contravariant Functors everywhere but why?
We don’t want to map … and change HttpApi[Json] into HttpApi[Ints]
We might want to:
● provide part of configuration
● change config file path to parsed case class with configuration
● set execution context for all operations
● provide dependency like AvroKafkaDeserializer and schema registry config
(clients don’t have to worry about it)
● add defaults and require only Option[Config]
● ...
39
40. Contravariant - examples in open source libs
Encoder in Scodec:
github.com/scodec/scodec/blob/series/1.11.x/shared/src/main/scala/scodec/Encoder.scala#L40-L47
Schedule/ZSink/ZQueue in ZIO (contramap, provideSome):
https://github.com/zio/zio/search?q=contramap&unscoped_q=contramap
Logger
https://github.com/zio/zio-logging/issues/7
JSON Encoder in Circe
https://github.com/circe/circe/blob/master/modules/core/shared/src/main/scala/io/circe/Encoder.scala#L53-L55
40
44. case class Serializer[A](run: A => Array[Byte])
val strSerial = Serializer[String](_.getBytes)
val intSerial = Serializer[Int](_.toString.getBytes)
GOTO Github:
https://github.com/lemastero/contravariant_profunctor_exercises/blob/master/src/test/scala/contra_pro/DivideSpec.scala
Divide (1) - Serialization
44
45. val fragmentSerial = Serializer[Fragment] { frag =>
val a1 = strSerial.run(frag.name)
val a2 = intSerial.run(frag.size)
a1 ++ a2
}
val serialized = fragmentSerial.run(Fragment("Area", 52))
new String(serialized ) mustBe "Area52"
Divide (2) - How to combine serializers?
45
51. Define full laws for Divide as in Haskell
hackage.haskell.org/package/contravariant/docs/Data-Functor-Contravariant-Divisible.html#g:4
Not simplified as in Scalaz and Cats!
Contravariant - Divide - Challenge
51
57. Good general theory does not search for the maximum
generality, but for the right generality.
Saunders Mac Lane
Choose the right level of abstraction, to see the
problem more clearly
Eugenia Cheng, Category in Life
https://www.youtube.com/watch?v=ho7oagHeqNc
Thank you :)
57