FunctionK

API Documentation: FunctionK

A FunctionK transforms values from one first-order-kinded type (a type that takes a single type parameter, such as List or Option) into another first-order-kinded type. This transformation is universal, meaning that a FunctionK[List, Option] will translate all List[A] values into an Option[A] value for all possible types of A. This explanation may be easier to understand if we first step back and talk about ordinary functions.

Ordinary Functions

Consider the following scala method:

def first(l: List[Int]): Option[Int] = l.headOption

This isn't a particularly helpful method, but it will work as an example. Instead of writing this as a method, we could have written this as a function value:

val first: List[Int] => Option[Int] = l => l.headOption

And here, => is really just some syntactic sugar for Function1, so we could also write that as:

val first: Function1[List[Int], Option[Int]] = l => l.headOption

Let's cut through the syntactic sugar even a little bit further. Function1 isn't really a special type. It's just a trait that looks something like this:

// we are calling this `MyFunction1` so we don't collide with the actual `Function1`
trait MyFunction1[A, B] {
  def apply(a: A): B
}

So if we didn't mind being a bit verbose, we could have written our function as:

val first: Function1[List[Int], Option[Int]] = new Function1[List[Int], Option[Int]] {
  def apply(l: List[Int]): Option[Int] = l.headOption
}

Abstracting via Generics

Recall our first method:

def first(l: List[Int]): Option[Int] = l.headOption

The astute reader may have noticed that there's really no reason that this method needs to be tied directly to Int. We could use generics to make this a bit more general:

def first[A](l: List[A]): Option[A] = l.headOption

But how would we represent this new first method as a =>/Function1 value? We are looking for something like a type of List[A] => Option[A] forAll A, but this isn't valid scala syntax. Function1 isn't quite the right fit, because its apply method doesn't take a generic type parameter.

Higher Kinds to the Rescue

It turns out that we can represent our universal List to Option transformation with something that looks a bit like Function1 but that adds a type parameter to the apply method and utilizes higher kinds:

trait MyFunctionK[F[_], G[_]] {
  def apply[A](fa: F[A]): G[A]
}

Cats provides this type as FunctionK (we used MyFunctionK for our example type to avoid confusion). So now we can write first as a FunctionK[List, Option] value:

import cats.arrow.FunctionK

val first: FunctionK[List, Option] = new FunctionK[List, Option] {
  def apply[A](l: List[A]): Option[A] = l.headOption
}

Syntactic Sugar

If the example above looks a bit too verbose for you, the kind-projector compiler plugin provides a more concise syntax. After adding the plugin to your project, you could write the first example as:

val first: FunctionK[List, Option] = λ[FunctionK[List, Option]](_.headOption)

Cats also provides a ~> type alias for FunctionK, so an even more concise version would be:

import cats.~>

val first: List ~> Option = λ[List ~> Option](_.headOption)

Being able to use ~> as an alias for FunctionK parallels being able to use => as an alias for Function1.

Use-cases

FunctionK tends to show up when there is abstraction over higher-kinds. For example, interpreters for free monads and free applicatives are represented as FunctionK instances.

Types with more than one type parameter

Earlier it was mentioned that FunctionK operates on first-order-kinded types (types that take a single type parameter such as List or Option). It's still possible to use FunctionK with types that would normally take more than one type parameter (such as Either) if we fix all of the type parameters except for one. For example:

type ErrorOr[A] = Either[String, A]

val errorOrFirst: FunctionK[List, ErrorOr] =
  λ[FunctionK[List, ErrorOr]](_.headOption.toRight("ERROR: the list was empty!"))

Natural Transformation

In category theory, a natural transformation provides a morphism between Functors while preserving the internal structure. It's one of the most fundamental notions of category theory.

If we have two Functors F and G, FunctionK[F, G] is a natural transformation via parametricity. That is, given fk: FunctionK[F, G], for all functions A => B and all fa: F[A] the following are equivalent:

fk(F.map(fa)(f)) <-> G.map(fk(fa))(f)

We don't need to write a law to test the implementation of the fk for the above to be true. It's automatically given by parametricity.

Thus natural transformation can be implemented in terms of FunctionK. This is why a parametric polymorphic function FunctionK[F, G] is sometimes referred as a natural transformation. However, they are two different concepts that are not isomorphic.

For more details, Bartosz Milewski has written a great blog post titled "Parametricity: Money for Nothing and Theorems for Free".