Getting started with recursion schemes using Matryoshka

Matryoshka is a Scala library that specializes on implementing recursion schemes, it has a mechanism to implicitly do the recursion for us instead of implementing by ourselves the recursive functions, it automates the process of traversing and evaluating the data structures.

So if you have many recursive functions in your code, you have to figure out firstly about your Data Structures design. because Data Structures and algorithms are patterns for solving problems, we are able to come up with more elegant solution, if we have a good design. In order to use Matryoshka we need to deal with recursive Data Structures.

1. Recursive Data Structure:

We need a recursive Data Structure: An ADT which refers itself.

Let’s take an example of the Russian dolls “Matryoshka dolls”, They are recursive, every doll is a small version of the other, and we have the smallest one (let’s call it Tiny).

this is our ADT

`sealed trait Matryoshkacase class Doll(name: String, daughter: Matryoshka) extends Matryoshkacase class Tiny(name: String) extends Matryoshka`

Goals:

• Build Matryoshka dolls from a list of names
• Calculate the number of dolls
• Transform `List[String]` to a `List[Person]` with its age (the smallest doll should be 6 years old, and each doll is 1 year older)
`case class Person(name: String, age: Int)`

2. Preparation to use Matryoshka

In order to use matryoshka we need to follow those steps:

a. Remove recursion

`sealed trait Matryoshkacase class Doll(name: String, daughter: ???) extends Matryoshkacase class Tiny(name: String) extends Matryoshka`

Why?

Matryoshka uses a generic recursion approach, we need to be able to evaluate our Data Structure to other value of any type we want, so we’re going to have a new generic parameter type and we replace the recursive reference with this type:

`sealed trait Matryoshka[T]case class Doll[T](name: String, daughter: T) extends Matryoshka[T]case class Tiny[T](name: String) extends Matryoshka[T]`

`T` might be Integer, String, Double, Person, Boolean…

b. Recapture Recursion

If we want to build a recursive Data Structure from any value, we need to define: `T` as `Matryoshka[T]` where `T` is another `Matryoshka[T]` .. If we have 10 dolls: `Matryoshka[Matryoshka[Matryoshka[Matryoshka[Matryoshka[Matryoshka[Matryoshka[Matryoshka[Matryoshka[Matryoshka[Nothing]]]]]]]]]]` 😳..

How can we generalize this?

We need to define `T` as another `Matryoshka[T]` : `T = Matryoshka[T]`

That remind us about the fixed point of a function is `f(x) = x` we need a fixed Point of the type `Matryoshka[_]`

Matryoshka library provides us this Fixed Point type:

`case class Fix[F[_]](unFix: F[Fix[F]])`

That’s exactly what we need, after we made our Data Structure with kind `* -> *` like `F[_]` : `Matryoshka[_]` we can use `Fix[Matryoshka]` to define our recursive type and Doll has daughter of type `Matryoshka[Fix[Matryoshka]]` (because we recapture the recursion and we made `T = Fix[Matryoshka]` )

Example:

`val dolls: Fix[Matryoshka] = Fix(Doll("Anna", Fix(Doll("Lisa", Fix(Tiny("Kate"))))))`

c. Functor

Functor is the main thing that makes recursion schemes work. Because matryoshka library requires to define an implicit scalaz Functor instance of our Data Structure to be able to traverse it and to do the recursive calls for us.

The idea is if we have `F[A]` and we have a function that comes from `A => B` this will give us `F[B]`

`implicit val dollsFunctorImpl: Functor[Matryoshka] = new Functor[Matryoshka] {  override def map[A, B](fa: Matryoshka[A])(f: A => B): Matryoshka[B] = fa match {    case Doll(n, d) => Doll(n, f(d))    case Tiny(s)    => Tiny(s)  }}`

3. Recursion schemes using matryoshka

let’s start with the first goal:

a. Build Matryoshka dolls from a list of names (non empty list!)

We need to have a function which comes from `NonEmptyList[String] => Matryoshka[_]`

`Coalgebra[F[_], A]` : is a type in matryoshka library that unfold a value A to a structure F[_], we can see it as a function: `A => F[_]`

`type Coalgebra[F[_], A] = A => F[A]`

In our case `F[_]` is `Matryoshka[_]` and `A` is `NonEmptyList[String]`

Let’s define our `Coalgebra[Matryoshka, NonEmptyList[String]]` !

`val coalgebra: Coalgebra[Matryoshka, NonEmptyList[String]] = {  case NonEmptyList(h, _: INil[String]) => Tiny(h)  case NonEmptyList(h, l) =>    val list: List[String] = l.toList    Doll(h, NonEmptyList(list.head, list.tail : _*))}`

So here we define which operation we perform at each step of the computation. We have a `List[String]` as an input and we will return `Matryoshka[List[String]]`

How it works?

`Coalgebra` will determine the next single step of the construction.

We need to call a recursive function that will go from top to down to build our Data Structure (Matryoshka dolls) from the List at the same time removing the recursive structure that we previously had.

`def ana[A](a: A)(f: Coalgebra[F, A])(implicit F: Functor[F]): F[A]`

anamorphism: “from the top form” is a generic function that can build a type of shape `F[_]`

`val names = NonEmptyList("a", "b", "c", "d")val result = names.ana[Fix[Matryoshka]](coalgebra)`

b. Calculate the number of dolls

We need to have a function which comes from `Matryoshka[_] => Int`

`Algebra[F[_], A]` : is a type in matryoshka library that fold a structure F to a value A, we can see it as a function: `F[_] => A`

`type Algebra[F[_], A] = F[A] => A`

So, let’s define our `Algebra[Matryoshka, Int]` , It’s very simple!

`val algebra: Algebra[Matryoshka, Int] = {  case Doll(_, d) => 1 + d  case Tiny(_) => 1}`

We evaluated in every level the computation.

How it works?

We need to collapse our Data Structure (Matryoshka dolls) down into a single integer value at the same time taking the recursive structure and collapsing it to get a single value

`def cata[A](f: Algebra[F, A])(implicit BF: Functor[F]): A`

catamorphism: “from the bottom to up form” is a generic function that can fold a value recursively into a single value of type `A`

`val dolls: Fix[Matryoshka] = Fix(Doll("Anna", Fix(Doll("Lisa", Fix[Matryoshka](Tiny("Kate"))))))`
`val result = dolls.cata[Int](algebra)`

3. Transform `List[String]` to a `List[Person]` with its age (the smallest doll should be 6 years old, and each doll is 1 year older)

We need: `List[String] => Matryoshka[_] => List[Person]`

We need to define a `Coalgebra[Matryoshka, List[String]]` and an `Algebra[Matryoshka, List[Person]]`

We have already implemented our `Coalgebra` Don’t forget we need to calculate the age of the person in our `Algebra` let’s do it!

`val algebraPerson: Algebra[Matryoshka, List[Person]] = {  case Doll(n, d) => d :+ Person(n, d.last.age + 1)  case Tiny(n)    => Person(n, 6) :: Nil}`

Cool, now we can call

`names.ana[Fix[Matryoshka]](coalgebra).cata[List[Person]](algebraPerson)`

But for better performance, we can use `hylo` to do that in one pass!

`names.hylo(algebraPerson, coalgebra)`

I hope you enjoyed reading this blog.

Now you can get started and implement more examples using Matryoshka.

If you’re interested to learn more about Matryoshka library, those are some interesting links: