Upgrade to Pro — share decks privately, control downloads, hide ads and more …

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.

Alessandro Lacava

May 13, 2017
Tweet

More Decks by Alessandro Lacava

Other Decks in Programming

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. They help you raise the abstraction level Talk is cheap.

    Show me the code. — Linus Torvalds Alessandro Lacava, @lambdista | Scala Italy 2017 | Rome
  5. 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
  6. Some type aliases type Keywords = List[Keyword] type Result[+A] =

    Either[String, A] Alessandro Lacava, @lambdista | Scala Italy 2017 | Rome
  7. 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
  8. 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
  9. 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
  10. 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
  11. 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
  12. 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
  13. 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
  14. 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
  15. 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
  16. 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
  17. 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
  18. 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
  19. 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
  20. 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
  21. 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
  22. 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
  23. Simple != Easy Simplicity is the ultimate sofistication — Leonardo

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

    let the buzzwords scare you → Always follow the types Alessandro Lacava, @lambdista | Scala Italy 2017 | Rome