"Type Disjunctions" in Scala
Tags: scala, functional, discuss
Published: 2019-11-06 15:37:35

Hi guys,

Today i'd like to talk about **Type Disjunctions** (Union Type) in Scala.

As we all know this feature is *not available* in Scala yet but i'd like to share with you my solution about a case i've had this week working on my library.

I'm writing a FP library to edit and work easily with XML in Scala, it's based on standard *scala-xml* library and *Cats*. You can check this library on github at:  [https://github.com/geirolz/advxml](https://github.com/geirolz/advxml)


# Case

I've defined a new type alias named *ValidatedEx*
```scala
    type ValidatedEx[+T] = ValidatedNel[Throwable, T] // = Validated[NonEmptyList[E], A]
```
I wanted to add new methods on this type using extension methods, one of these was a method named "transform" with the aim to convert  *ValidatedEx* instance into *F[_]*.

**Must have:**

* One single method, the user should not have the burden to choose the right method depending on the case.
* Use Cats instances, i don't want to reinventing the wheel.

# First solution [Failed]

Using an implicit monad error in order to convert *ValidatedEx* to *F[_]*, pattern matching over the instance, invoking *pure* or *raiseError* depending on the case and the game is done!

```scala
    implicit class ValidatedExOps[A](validated: ValidatedEx[A]) {
        def transform[F[_]](implicit F: MonadError[F, NonEmptyList[Throwable]]): F[A] =
          validated match {
            case Valid(a)   => F.pure(a)
            case Invalid(e) => F.raiseError(e)
          }
      }
```

Yea! It works! But...i can't use standard monads error provided by cats...

This wont compile because in the scope is not present an implicit instance of *MonadError[Try, NonEmptyList[Throwable]]* and cats provides only *MonadError[Try, Throwable].*

```scala
      import cats.instances.try_._
      val value: ValidatedEx[String] = Valid("TEST")
      //No implicit found for parameter F:MonadError[Try, NonEmptyList[Throwable]] 
      val tryValue: Try[String] = value.transform[Try]
```

I don't want to create a new monad error for each type, my library must work with cats!

# Second solution [Failed]

Using *natural transformation!*

```scala
      implicit class ValidatedExOps[A](validated: ValidatedEx[A]) {
        def transform[F[_]](implicit F: ValidatedEx ~> F): F[A] = F(validated)
      }
    
      import cats.instances.try_._
      val value: ValidatedEx[String] = Valid("TEST")
      //No implicit found for parameter F:ValidatedEx ~> F
      val tryValue: Try[String] = value.transform[Try]
```

Here we go again, this solution require ad-hoc implementations of *FunctionK* for each type and i don't want to reinventing the wheel.

# Third solution [Success]

I want have just one method, keep all possibilities open and substantially have two MonadError in the signature but only one is required. This smell like an union type!

Let's define "union types" using Either with a very easy solution.
```scala
      type \/[+A, +B]           = Either[A, B]
      type MonadEx[F[_]]        = MonadError[F, Throwable]
      type MonadNelEx[F[_]]     = MonadError[F, NonEmptyList[Throwable]]
      
      implicit class ValidatedExOps[A](validated: ValidatedEx[A]) {
        def transform[F[_]](implicit F: MonadEx[F] \/ MonadNelEx[F]): F[A] =
          F match {
            case Left(m) =>
              validated match {
                case Valid(value) => m.pure(value)
                //AggregatedException is a my own class that just collapse multiple exceptions into one 
                case Invalid(exs) => m.raiseError(new AggregatedException(exs.toList))
              }
            case Right(m) =>
              validated match {
                case Valid(value) => m.pure(value)
                case Invalid(exs) => m.raiseError(exs)
              }
          }
      }
```

But...still fail, obviously there isn't an implicit instance for *Either[MonadEx[F], MonadNelEx[F]]*

```scala
      import cats.instances.try_._
      val value: ValidatedEx[String] = Valid("TEST")
      //No implicit found for parameter F:Either[MonadEx[F], MonadNelEx[F]]
      val tryValue: Try[String] = value.transform[Try]
```

Let's wrap what cats provides, let's "intercept" monads error provided in the scope and wrap them into Either.

```scala
      implicit def monadExLeftDsj[F[_]]
        (implicit F: MonadEx[F]): MonadEx[F] \/ MonadNelEx[F] = Left(F)
      implicit def monadNelExRightDsj[F[_]]
        (implicit F: MonadNelEx[F]): MonadEx[F] \/ MonadNelEx[F] = Right(F)
```

And for a more generic solution we can write:
```scala
      //for explicit values
      implicit def aLeftDsj[A](v: A): A \/ * = Left(v)
      implicit def bRightDsj[B](v: B): * \/ B = Right(v)

      //for implicit values
      implicit def aLeftDsj[A](implicit v: A): A \/ * = Left(v)
      implicit def bRightDsj[B](implicit v: B): * \/ B = Right(v)
```

Done! It works!

I know that this solution isn't perfect and beautiful, i hate implicit conversions, and i also know that union types are coming but in the meantime this is a simple solution to partially bypass this scala loss for a specific case like mine.

Tell me what do you think about this solution :)