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

    View full-size slide

  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

    View full-size slide

  3. The real question is: why should I care
    about monads?
    Alessandro Lacava, @lambdista | Scala Italy 2017 | Rome

    View full-size slide

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

    View full-size slide

  5. They help you raise the abstraction level
    Talk is cheap. Show me the code.
    — Linus Torvalds
    Alessandro Lacava, @lambdista | Scala Italy 2017 | Rome

    View full-size slide

  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

    View full-size slide

  7. Some type aliases
    type Keywords = List[Keyword]
    type Result[+A] = Either[String, A]
    Alessandro Lacava, @lambdista | Scala Italy 2017 | Rome

    View full-size slide

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

    View full-size slide

  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

    View full-size slide

  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

    View full-size slide

  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

    View full-size slide

  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

    View full-size slide

  13. Keyword Validator
    Keywords => Result[Keywords]
    Alessandro Lacava, @lambdista | Scala Italy 2017 | Rome

    View full-size slide

  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

    View full-size slide

  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

    View full-size slide

  16. DRY (Don't Repeat Yourself)
    Alessandro Lacava, @lambdista | Scala Italy 2017 | Rome

    View full-size slide

  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

    View full-size slide

  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

    View full-size slide

  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

    View full-size slide

  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

    View full-size slide

  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

    View full-size slide

  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

    View full-size slide

  23. Can we do
    better?
    Alessandro Lacava, @lambdista | Scala Italy 2017 | Rome

    View full-size slide

  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

    View full-size slide

  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

    View full-size slide

  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

    View full-size slide

  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

    View full-size slide

  28. Simple != Easy
    Simplicity is the ultimate sofistication
    — Leonardo Da Vinci
    Alessandro Lacava, @lambdista | Scala Italy 2017 | Rome

    View full-size slide

  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

    View full-size slide

  30. Questions?
    → twitter: @lambdista
    → blog: http://www.lambdista.com
    → github: https://github.com/lambdista
    Alessandro Lacava, @lambdista | Scala Italy 2017 | Rome

    View full-size slide