Maybe Strikes Back
Tags: java, scala, kotlin
Published: 2023-07-01 13:29:05

In the end of the previous chapter, we've stated that Kotlin's `Any?` is a proper union type. Also, multiple handy operators and functions to work with `Any?` exist as part of the standard library. So, based on these two statements, Kotlin's `null` handling has been inferred to be "the best". Today I'm going to review its weaker points. Again, we'll be looking at Haskell and its `Maybe` type for inspiration. A lot of language designers do this anyway, why can't we?

## Universal Interface

With a risk of sounding like a broken record, I would state that the main power of `Maybe` comes from the universal `Monad` type class. `Maybe` itself may not be the best answer to nullability question, but its interface answers to dozens of other questions at the same time. This leads to a unified usage and thus to more readable and re-usable code. And it's completely reasonable from the OOP developer point of view. We all have heard the popular mantra of "code to interface". And here Haskell does exactly that.

The key function of monad, `bind`, has signature `m a -> (a -> m b) -> m b`. Rewriting this using Java-like types may look clearer: `Monad<A> -> Function<A, Monad<B>> -> Monad<B>`. Or even more Javishly: `Monad<B> bind(Monad<A> m, Function<A, Monad<B>> m)`. Does this look familiar? It should. It's a quite popular method among Java codebases, commonly known as `flatMap`. But where is the problem with such a popular method?

Let's see 3 core examples of it:

```java
Optional<A>.flatMap(Function<A, Optional<B>>) -> Optional<B>

Stream<A>.flatMap(Function<A, Stream<B>>) -> Stream<B>

CompletableFuture<A>.thenCompose(Function<A, CompletableFuture<B>>) -> CompletableFuture<B>
```

* I'm cleaning up some type noise for brevity here
* A lot more examples exist in the wild, but to showcase the idea these are enough
* `CompletableFuture` is my favorite: to make matters worse, it doesn't even try to conform to usual naming

## Kinds of Types

My OOP training screams to abstract a common parent out of the same type signatures. To achieve this, we need an interface looking like the following one:

```java
interface Monad<A> {
    Monad<B> flatMap(Function<A, Monad<B>> f);
}
```

The problem here is that it's not generic enough. It will return us `Monad`s where we would want `Optional`s and `Stream`s. For example, expression `optional.flatMap(...)` will have parent's type of `Monad<B>` not child's `Optional<B>`. So, after such `flatMap` we wouldn't be able to chain `Optional`-specific methods anymore (without unholy casts, obviously).

If we go back to Haskell:

```haskell
class Monad m where
  (>>=) :: m a -> (a -> m b) -> m b
```

We'll spot that Haskell declares container itself as a generic type parameter (note the `m` in `m a`). Can Java do the same? I wasn't able to achieve it. If you succeed, you can destroy the reasoning of this article. Please go ahead.

No point in showing all the different type signature combinations I've tried. They either do not compile, or (as with initial "naive" `Monad<A>`) do not return child type. The prettiest example, just to illustrate my thought-flow:

```java
interface Monad<A, C extends Monad<A, C>>
```

Can be extended by:

```java
sealed interface Maybe<A> extends Monad<A, Maybe<A>> permits Just, Nothing
```

Which narrows our `Monad` parent type to proper `Maybe` child type and compiles using Java 17. The problem arises when we want to return `Maybe<B>` from `Maybe<A>` method. Our `Monad.flatMap` could look like the following (note that we're using the same trick of `D extends Monad` we used successfully on class level):

```java
<B, D extends Monad<B, D>> D flatMap(Function<A, D> f)
```

And, consequently, `Maybe.flatMap`:

```java
<B> Maybe<B> flatMap(Function<A, Maybe<B>> f)
```

Which doesn't compile with: `flatMap(Function<A, Maybe<B>>)' in 'Maybe' clashes with 'flatMap(Function<A, D>)' in 'Monad';`. Even though `Maybe<B>` extends `Monad<B, Maybe<B>`, the compiler doesn't substitute `D` with it, as it did with `C` on class level previously.

Generally speaking, it's a known limitation of the Java compiler (as well as Kotlin's). To get more information on the matter, search for *higher-kinded types*. There are multiple Java libraries trying to add HKT, but at the moment of writing all of them introduce themselves as "not production-ready" ones. Also, it sounds like HKT should be part of a language itself to be efficient enough. So, unfortunately for now we are stuck with "code to implementation" in Java-world.

## Imperative Paradigm

In Haskell, the universal monadic interface allows us to write both declarative and (surprisingly for a purely functional language) imperative code. Let's look at a simple example. Two ways of fetching some entity from some database, populating some metadata from some external API, and then mapping the result to a DTO.

Declarative:

```haskell
fetchDto id = fetchEntityFromDB id >>= populateMeta <&> toDto
```

Imperative:

```haskell
fetchDto id = do
  entity <- fetchEntityFromDB id
  entityWithMeta <- populateMeta entity
  let dto = toDto entityWithMeta
  return dto
```

Which code looks more familiar and thus more readable for Java developers without no functional background (i.e. for an average coder prior to Java 8)? If you are not familiar with Haskell, take your time to comprehend and compare both examples, they are really simple.

So?.. Right you are. The first one! At least, that is what we've got in Java 8. Am I hearing some weak voices for the second approach? Fear no more, you're not alone in this. There are examples of imperative handling of `Monad`s on the JVM. Not in Java itself, but Kotlin's `suspend/await` lets you write monadic code imperatively, exactly like `do` in Haskell (to be fair, similarly to lots of other `async/await` implementations). Yes, we are stepping out of our cozy `Maybe`-only shell to a wider land of different `Monad`s here. But we'll return, promise.

Java 8:

```java
CompletableFuture<Dto> fetchDto(String id) {
  return fetchEntityFromDb(id)
      .thenCompose(this::populateMeta)
      .thenApply(this::toDto);
}
```

Kotlin:

```kotlin
suspend fun fetchDto(id: String): Dto {
    val entity = fetchEntityFromDb(id)
    val entityWithMeta = populateMeta(entity)
    val dto = toDto(entityWithMeta)
    return dto
}
```

In Kotlin, we can shorten this function to a single line by using extension methods. That's not the point. The point is that we have a choice between imperative and declarative paradigms for asynchronous logic.

That's cool, but let's say that we want to add support for some other monadic type. For example, our beloved `T?`. How much code would we need to change in Kotlin?

```kotlin
fun fetchDto(id: String): Dto? {
    val entity = fetchEntityFromDb(id)
    val entityWithMeta = entity?.let { populateMeta(it) }
    val dto = entityWithMeta?.let { toDto(it) }
    return dto
}
```

Huh, not DRY at all! How many lines we would need to change in Haskell?.. Zero, since we aren't using anything implementation-specific. Coding to an interface, remember that one?

## The Union Way

As we already know, another mathematically correct nullable type resides in Scala. Does it adhere to the monadic interface? Can we implement one? 

Scala lacks an explicit trait for Monad, however has an implicit interface that is used by for-comprehensions, which in turn are extremely similar to Haskell's `do` blocks we've already seen. Here is one possible implementation for unionized `Maybe` type:

```scala
extension[A] (m: A | Null)
  def map[B](f: A => B): B | Null = m match
    case null => null
    case _ => f(m.nn)

  def flatMap[B](f: (=> A) => B | Null): B | Null = m match
    case null => null
    case _ => f(m.nn)

  def withFilter(f: A => Boolean): A | Null = m match
    case null => null
    case _ => if f(m.nn) then m.nn else null
```

Next is our good ol' sample function:

```scala
def fetchDto(id: String) = for {
  entity <- fetchEntityFromDb(id)
  entityWithMeta <- populateMeta(entity)
  dto <- toDto(entityWithMeta)
} yield dto
```

And guess what? We can change our `Maybe` Monad to any other type implementing the same contract and this code will not change. 

Let's re-check backward compatibility rules from the previous chapter. In this example, we can narrow down the return type in the signature of the `populateMeta` from `Entity -> EntityWithMeta | Null` to `Entity -> EntityWithMeta`. And as expected, client code is not broken. The same function compiles and works correctly!

This actually leads us to the interesting side effect. We now can work with non-nullable types using monadic pipelines:

```scala
val i: Int = 5
val j: Int | Null = i.map(_ - 3)
```

The one major drawback of our `A | Null` type I see is that if we want to make it actually useful, we need to re-invent the wheel (i.e. re-implement all the functionality, like `zip` and `orElse`, from the `Option` type). Can you spot any other problems?

---

All in all, by this moment we've covered quite an interesting evolutionary path from Java 7 to Scala 3 union types. From a theoretical standpoint, the latter now seem superior to the available alternatives (which doesn't mean that it's ideal, does it?). In the next chapter (if it ever gets published), I'm planning to abstract out the notion of an "absent value" even more. `Null` is too specific. "Code to interface", still remember that one?