Raising the abstraction level with HKTs and type classes

Raising the abstraction level with HKTs and type classes

I suppose it's an objective of every developer writing reusable code. Statically typed FP and higher-kinded types lets you raise the abstraction level enormously with respect to OOP and, more generally, languages which don't have HKTs and type classes.

In this talk you'll see how to take advantage of HKTs to implement a very reusable function. I'll start by implementing it using a quick & dirty approach and then trying to make it more abstract and reusable thanks to more or less known type classes, actually making sense of them so you can see they are not just confined to theory and useless in practice.

2c14a9713460e05479a7ce756078abea?s=128

Alessandro Lacava

May 13, 2017
Tweet

Transcript

  1. A monad is just monoid in the category of endofunctors.

    What's the problem with this sentence? Alessandro Lacava, @lambdista | Scala Italy 2017 | Rome
  2. At least two problems → It's scary if you're not

    a math junkie → It's true! Alessandro Lacava, @lambdista | Scala Italy 2017 | Rome
  3. The real question is: why should I care about monads?

    Alessandro Lacava, @lambdista | Scala Italy 2017 | Rome
  4. Very common answers Effects. Huh? Sequencing. ... Alessandro Lacava, @lambdista

    | Scala Italy 2017 | Rome
  5. They help you raise the abstraction level Talk is cheap.

    Show me the code. — Linus Torvalds Alessandro Lacava, @lambdista | Scala Italy 2017 | Rome
  6. Advertising service Very small and simplified subset of the model

    sealed trait KeywordMatchType case object Broad extends KeywordMatchType case object Exact extends KeywordMatchType case object Phrase extends KeywordMatchType final case class Keyword(text: String, matchType: KeywordMatchType) Alessandro Lacava, @lambdista | Scala Italy 2017 | Rome
  7. Some type aliases type Keywords = List[Keyword] type Result[+A] =

    Either[String, A] Alessandro Lacava, @lambdista | Scala Italy 2017 | Rome
  8. Keyword Expander Keywords => Future[Keywords] Alessandro Lacava, @lambdista | Scala

    Italy 2017 | Rome
  9. Write a function that reduces an expander list def foldFutureKeywords(fs:

    List[Keywords => Future[Keywords]]) (init: Keywords): Future[Keywords] = ??? What will you use to reduce a List? Alessandro Lacava, @lambdista | Scala Italy 2017 | Rome
  10. First quick & dirty attempt def foldFutureKeywords(fs: List[Keywords => Future[Keywords]])

    (init: Keywords): Future[Keywords] = fs.foldLeft(Future.successful(init)) { (acc, f) => acc.flatMap(f) } Alessandro Lacava, @lambdista | Scala Italy 2017 | Rome
  11. Usage val keywordExpanders: List[Keywords => Future[Keywords]] = ??? val expand:

    Keywords => Future[Keywords] = foldFutureKeywords(keywordExpanders) → What's the problem? Alessandro Lacava, @lambdista | Scala Italy 2017 | Rome
  12. Improved version def foldFuture[A](fs: List[A => Future[A]]) (init: A): Future[A]

    = fs.foldLeft(Future.successful(init)) { (acc, f) => acc.flatMap(f) } Alessandro Lacava, @lambdista | Scala Italy 2017 | Rome
  13. Keyword Validator Keywords => Result[Keywords] Alessandro Lacava, @lambdista | Scala

    Italy 2017 | Rome
  14. Write a function that reduces a validator list def foldEither[A](fs:

    List[A => Result[A]]) (init: A): Result[A] = fs.foldLeft(Right(init): Result[A]) { (acc, f) => acc.flatMap(f) } Alessandro Lacava, @lambdista | Scala Italy 2017 | Rome
  15. Usage val keywordValidators: List[Keywords => Result[Keywords]] = ??? val validate:

    Keywords => Result[Keywords] = foldEither(keywordValidators) → What's the problem? Alessandro Lacava, @lambdista | Scala Italy 2017 | Rome
  16. DRY (Don't Repeat Yourself) Alessandro Lacava, @lambdista | Scala Italy

    2017 | Rome
  17. Repetions def **foldFuture**[A](fs: List[A => **Future**[A]]) (init: A): **Future**[A] =

    fs.foldLeft(**Future.successful(init)**) { (acc, f) => acc.flatMap(f) } def **foldEither**[A](fs: List[A => **Result**[A]]) (init: A): **Result**[A] = fs.foldLeft(**Right(init): Result[A]**) { (acc, f) => acc.flatMap(f) } Alessandro Lacava, @lambdista | Scala Italy 2017 | Rome
  18. Higher-kinded types Symbol Kind Examples * Simple type Int, String,

    Double * -> * Unary type constructor List[_], Option[_], Future[_] * -> * -> * Binary type constructor Either[_, _], Function1[_, _], Map[_, _] (* -> *) -> * Higher-order type operator, higher-kinded type for friends Foo[F[_]], Bar[G[_]], Functor[F[_]], Monad[M[_]] Alessandro Lacava, @lambdista | Scala Italy 2017 | Rome
  19. Monad for developers trait Monad[M[_]] extends Applicative[M] { def pure[A](a:

    A): M[A] def flatMap[A, B](ma: M[A])(f: A => M[B]): M[B] // ... } → It's a type class that is polymorphic over a unary type constructor (M[_]) → It has two abstract methods → Its instances must respect some laws Alessandro Lacava, @lambdista | Scala Italy 2017 | Rome
  20. Back to our repeated code def **foldFuture**[A](fs: List[A => **Future**[A]])

    (init: A): **Future**[A] = fs.foldLeft(**Future.successful(init)**) { (acc, f) => acc.flatMap(f) } def **foldEither**[A](fs: List[A => **Result**[A]]) (init: A): **Result**[A] = fs.foldLeft(**Right(init): Result[A]**) { (acc, f) => acc.flatMap(f) } Alessandro Lacava, @lambdista | Scala Italy 2017 | Rome
  21. Factored out def foldList[M[_], A](fs: List[A => M[A]]) (init: A)

    (implicit M: Monad[M]): M[A] = fs.foldLeft(M.pure(init)) { (acc, f) => M.flatMap(acc)(f) } → Let's give it a spin! Alessandro Lacava, @lambdista | Scala Italy 2017 | Rome
  22. val keywordExpanders: List[Keywords => Future[Keywords]] = ??? val keywordValidators: List[Keywords

    => Result[Keywords]] = ??? val expand: Keywords => Future[Keywords] = foldList(keywordExpanders) val validate: Keywords => Result[Keywords] = foldList(keywordValidators) → ! Alessandro Lacava, @lambdista | Scala Italy 2017 | Rome
  23. Can we do better? Alessandro Lacava, @lambdista | Scala Italy

    2017 | Rome
  24. Sure Let's try to abstract over this List sh*t def

    foldList[M[_], A](fs: List[A => M[A]]) (init: A) (implicit M: Monad[M]): M[A] = fs.foldLeft(M.pure(init)) { (acc, f) => M.flatMap(acc)(f) } Alessandro Lacava, @lambdista | Scala Italy 2017 | Rome
  25. def foldTraversable[M[_], A](fs: GenTraversableOnce[A => M[A]]) (init: A) (implicit M:

    Monad[M]): M[A] = fs.foldLeft(M.pure(init)) { (acc, f) => M.flatMap(acc)(f) } Alessandro Lacava, @lambdista | Scala Italy 2017 | Rome
  26. Can we do even better? → Yes. Before I said

    that version worked for all subclasses of GenTraversableOnce. → I made a mistake. I opted for subtype polymorphism instead of parametric and ad hoc one. → Is there a type class which represent the foldable concept? → Yep, Foldable! Alessandro Lacava, @lambdista | Scala Italy 2017 | Rome
  27. Final version def fold[M[_], F[_], A](fs: F[A => M[A]]) (init:

    A) (implicit M: Monad[M], F: Foldable[F] ): M[A] = F.foldLeft(fs, M.pure(init)) { (fa, f) => M.flatMap(fa)(f) } → How beautiful and simple that is! Alessandro Lacava, @lambdista | Scala Italy 2017 | Rome
  28. Simple != Easy Simplicity is the ultimate sofistication — Leonardo

    Da Vinci Alessandro Lacava, @lambdista | Scala Italy 2017 | Rome
  29. Takeaways → Monads & co. are practically useful → Don't

    let the buzzwords scare you → Always follow the types Alessandro Lacava, @lambdista | Scala Italy 2017 | Rome
  30. Questions? → twitter: @lambdista → blog: http://www.lambdista.com → github: https://github.com/lambdista

    Alessandro Lacava, @lambdista | Scala Italy 2017 | Rome