In my previous post, I discussed using `Invariant`

to
add behaviour to value classes. Unfortunately, `Invariant`

is not powerful enough to provide instances for higher-kinded
type classes like `Functor`

or `Traverse`

. In this post, I’ll introduce `InvariantK`

, a type class I’ve written to solve
this limitation.

## Value classes with a type parameter

As I discussed previously, wrapping common types in dedicated classes (“value classes”) comes with a number of advantages. Value classes have a semantic meaning that more common types do not, and we can use this technique to get help from the compiler to avoid bugs.

Sometimes, these principles apply to generic types in the same way as they apply to normal types.

Consider this type called `Uncertain`

.

```
sealed trait Uncertain[+A]
case object Unknown extends Uncertain[Nothing]
final case class Known[A](a: A) extends Uncertain[A]
```

`Uncertain`

represents some value that we may know or not know. It is obviously very similar to `Option`

. `Known`

maps
directly to `Some`

, and `Unknown`

maps to `None`

. But `Uncertain`

has a different semantic meaning, which is now clear
everywhere we use it.

Compare:

```
final case class Person(
name: String,
age: Option[Int],
)
```

versus

```
final case class Person(
name: String,
age: Uncertain[Int],
)
```

The meaning of the `age`

field in the first case is ambiguous and a little silly. Every person has an `age`

, so how
could it ever be `None`

? But if we use `Uncertain`

, it becomes clear what we’re attempting to model. We are handling the
case where we don’t know the person’s age.

## Using `Invariant`

to define instances for `Uncertain`

In my last post
I described how we can use the `imap`

function provided by `Invariant`

to easily define type class instances based on an underlying type. Given `Uncertain`

is just like `Option`

, can we do
something similar here?

First of all, we’ll need two functions for converting between `Option[A]`

and `Uncertain[A]`

:

```
def optionToUncertain[A](option: Option[A]): Uncertain[A] = option match {
case Some(a) => Known(a)
case None => Unknown
}
def uncertainFromOption[A](uncertain: Uncertain[A]): Option[A] = uncertain match {
case Known(a) => Some(a)
case Unknown => None
}
```

For simple cases, using `imap`

works well. For example, we can define an `Eq`

instance easily:

```
import cats._
import cats.syntax.all._
implicit def eqForUncertain[A: Eq]: Eq[Uncertain[A]] = Eq[Option[A]].imap(optionToUncertain)(uncertainFromOption)
Eq[Uncertain[Int]].eqv(Known(1), Known(1)) // true
Eq[Uncertain[Int]].eqv(Known(1), Unknown) // false
```

But what about the more complex operations on `Option`

like `.map`

? This comes from the `Functor`

type class. If we had a `Functor`

we could do useful things like describing whether a `Person`

is over 18:

```
implicit val functorForUncertain: Functor[Uncertain] = ???
val p1 = Person(name = "👶", age = Known(3))
val p2 = Person(name = "💆", age = Unknown)
def isAdult(age: Int): Boolean = age >= 18
p1.age.map(isAdult) // 🙅 Known(false)
p2.age.map(isAdult) // 🤷 Unknown
```

Unfortunately, * Invariant is not powerful enough to define a Functor for Uncertain*. This is because

*. Put another way, whereas type classes like*

`Functor`

is
a higher-kinded type class`Eq[A]`

and `Semigroup[A]`

accept a simple type
parameter, `Functor[F[_]]`

only applies to types that themselves have a type parameter. We might say that the `F[_]`

in
`Functor[F[_]]`

has to “have a type hole” or “be a type constructor”.We need to find another solution.

## The `InvariantK`

type class

The solution is to create a type class similar to `Invariant`

, but which works on higher-kinded types rather than normal
types. There is a similar distinction between functions (which act on normal types) and `FunctionK`

.
Accordingly, what we need is something called `InvariantK`

.

Consider how `Invariant`

is defined (cf. Cats implementation):

```
trait Invariant[F[_]] {
def imap[A, B](fa: F[A])(f: A => B)(g: B => A): B
}
```

If we move each concept in this definition “up” by one “kind”, we end up with:

```
import cats.~>
trait InvariantK[T[_[_]]] {
def imapK[F[_], G[_]](tf: T[F])(f: F ~> G)(g: G ~> F): T[G]
}
```

This is pretty mind-bending. `T[_[_]]`

is a doubly-higher-kinded type! And notice that `f`

and `g`

are now instances of
`FunctionK`

(using the `~>`

notation) to transform between the two higher-kinded types `F[_]`

and `G[_]`

.

I’ve implemented `InvariantK`

in my personal library of Cats utilities. It’s available here.
We can use it to define a `Functor`

for `Uncertain`

:

```
import au.id.tmm.utilities.cats.classes.InvariantK
import au.id.tmm.utilities.cats.syntax.all._
import cats._
import cats.arrow.FunctionK
import cats.syntax._
val optionToUncertain: Option ~> Uncertain = new FunctionK[Option, Uncertain] {
override def apply[A](option: Option[A]): Uncertain[A] = option match {
case Some(a) => Known(a)
case None => Unknown
}
}
val uncertainToOption: Uncertain ~> Option = new FunctionK[Uncertain, Option] {
override def apply[A](uncertain: Uncertain[A]): Option[A] = uncertain match {
case Known(a) => Some(a)
case Unknown => None
}
}
implicit val functorForUncertain: Functor[Uncertain] = Functor[Option].imapK(optionToUncertain)(uncertainToOption)
val p1 = Person(name = "👶", age = Known(3))
val p2 = Person(name = "💆", age = Unknown)
def isAdult(age: Int): Boolean = age >= 18
p1.age.map(isAdult) // 🙅 Known(false)
p2.age.map(isAdult) // 🤷 Unknown
```

Once we have `Option ~> Uncertain`

and `Uncertain ~> Option`

, we can define a `Functor`

for `Uncertain`

. In fact, we can
use these `FunctionK`

instances to define a number of type class instances for `Uncertain`

:

```
implicit val monadErrorForUncertain: Monad[Uncertain] = Monad[Option].imapK(optionToUncertain)(uncertainToOption)
implicit val traverseForUncertain: Traverse[Uncertain] = Traverse[Option].imapK(optionToUncertain)(uncertainToOption)
implicit val monoidKForUncertan: MonoidK[Uncertain] = MonoidK[Option].imapK(optionToUncertain)(uncertainToOption)
```

Check out `tmmUtils`

. It has a bunch of utilities I’ve found useful, including
`InvariantK`

.