/ scala

TOTD: Scala - Natural Transformations

TL;DR Scroll to the end and hit that REPL link.

Let's combine what we've already learned about Functors and Functions as a quick refresher:

// Functionally equivalent //
def stringify(n: Int): String
val stringify1: Int => String

That => operator is syntactic sugar around Function1[A, B], therefore, this is also equivalent:

val stringify2: Function1[Int, String]

In Category Theory, this is considered a morphism. In the category of types, we can say we have an arrow from A to B. Now suppose we have two functors, F[A] and G[A]. A morphism, aka an arrow, from F to G is possible in the category of functors. This can be written as follows:

val stringify3: FunctionK[F, G]

What this means is that we now have an arrow from F to G in the category of functors. This is called a natural transformation. The idea is to preserve the internal structure while providing a different context, or wrapper for your internal structure.

Please note that the 1 in the Function1 above is referring to the arity of the function. It takes 1 argument. The K in FunctionK represents that this typeclass effects higher-kinded types, which happen to be functors. Fortunately, FunctionK has syntactic sugar similar to the function arrows.

val stringify4: F ~> G

We've been talking about functors in terms of F and G, so here's a couple concrete examples:

val stringify5: List ~> Option
val stringify6: Option ~> Id

Note that List[_], Option[_], Id[_], and other similar functors preserve the inner shape. Although natural transformations are usually used in conjunction with Inject and Free, they are not mutually exclusive.

Meet Scastie, an online REPL where you can try out what we spoke about here: https://scastie.scala-lang.org/animatedlew/iZflMSoXQEezn9KCk1ahKA/4