Skip to content

Instantly share code, notes, and snippets.

@eggm0n
Created May 11, 2018 14:26
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save eggm0n/ef12ab28231ba44560e19d3e478bbaf6 to your computer and use it in GitHub Desktop.
Save eggm0n/ef12ab28231ba44560e19d3e478bbaf6 to your computer and use it in GitHub Desktop.
Higher kinded types notes

F is a higher kinded type/function at the type level/type level constructor. Examples are Future, Option, List, etc.

List is a type constructor and Int is a proper type. If I apply Int to List then I get List[Int]which is a proper type. So far, all we have done is used a generic higher kinded type (F) instead of a concrete one (Future).

Typeclasses are a form of ad-hoc polymorphism. They allow you to an enrich an existing type with new capabilities. For example, let’s say I wanted to add two Orders together. The source code for an Order isn’t in our control but we would like to extend it.

def add[A](x: A, y: A)(implicit a: Addable[A]): A = a.add(x, y)

This is an example of a typeclass method. Notice that in order to use the addmethod, your A’s need to have a typeclass implementation of Addable[A].

There’s a more succinct syntax called the Context Bound that can be used for the add function. That looks like this:

def add[A: Addable](x: A, y: A): A = {
  val addable: Addable[A] = implicitly[Addable[A]]
  addable.add(x, y)
}

Previously, we saw that Future made use of flatMap, map and successful(also known as pure). 

Whilst our Addable typeclass abstracted over proper types like Int and Order, the Monad typeclass abstracts over higher kinded types like Future, List, Option, etc.

The main idea is that you can constrain the F effect to have a Monad typeclass implementation so we can make use of flatMap, map and pure.

class OrderService[F[_]](orderRepository: OrderRepository[F])(implicit monad: Monad[F]) {
  def get(orderId: UUID): F[Option[Order]] = orderRepository.get(orderId)
}

F must has a Monad typeclass implementation and this is why we are able to use flatMap, map and pure. 

There is an alternate form known as interface syntax which augments the F effect so it appears as if flatMap and map can be invoked on instances of F[A] but under the hood it’s using implicit classes to make this happen. All you do is import cats.syntax.all._and it brings a set of implicit classes into scope which enrich any instance of a type that has a Monad implementation and will allow you to invoke flatMap, map and pure. Since we no longer explicitly use the monad instance anymore, we use the more succinct context bound syntax to perform the typeclass constraint on F.

class OrderService[F[_]: Monad](orderRepository: OrderRepository[F]) {
  def get(orderId: UUID): F[Option[Order]] = orderRepository.get(orderId)
}

There are more powerful constraints available like MonadError where an effect can encapsulate errors (think Future and Task) or MonadFilter if you want to use flatMap, map and filter. You don’t need to place the constraint at the class level like we did above with OrderService. You can also place constraints at the function level.

Here fetchOrders can be done in parallel so we ask for an Applicative constraint but updateOrder requires sequencing so we ask for a Monad constraint.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment