Functional Effect System in Kotlin

Alexey Soshin
Jul 21, 2020 · 4 min read
Photo by Katya Austin on Unsplash

A few weeks ago my friend Dmitry Karlinsky wrote an article about “Demystifying functional effect systems in Scala”, which was a fascinating read. In less that fifty lines of code Dmitry has built an IO Monad, similar to what users of ZIO/Cats-Effect/Monix are familiar with.

This article prompted me to start thinking what a similar solution would look like in Kotlin. Would that be even possible, having a type system which is in many ways simpler than the one Scala language has?

As an exercise, let’s try to build a similar system in Kotlin, and see where we end up.

Note: all Scala code in this article is credited to Dmitry Karlinsky

IO Monad

Let’s start with building our Kotlin IO Monad, which we’ll call KIO, in a very un-imaginative manner.

Whereas in Scala we would do something like:

In Kotlin same algebra will look similar to this:

Sealed traits and case classes in Scala are quite similar to sealed classes and data classes in Kotlin, so the change isn’t dramatic.

I decided not to use single line declarations for map and flatMap in Kotlin, to add a bit more clarity on what is going on there.

Basically, what we’re saying is that our program can be described as a chain of events.

Those events can be one of two types:

  • SideEffect that provides us some input from the outside world (notice it does not get anything from us — ()->A
  • InvokeFunction — that provided a function and an input value, returns us some output — (A)->B

Runtime

When I wrote my first purely-functional program back in the day, it looked something like this:

fun main() {
SideEffect {
println("Hello World")
}
}

Note: it was written in Scala and Tagless Final, actually, but nevermind that

When I ran it, nothing happened. It wasn’t immediately obvious to me why at that time, because nothing is obvious in Tagless Final.

But looking at the code above now, you can clearly see why. SideEffect is just a data class that wraps a function. But nothing invokes this function. It’s basically dead code.

In Scala, Runtime may look something like this:

And here’s my version ofRuntime in Kotlin:

Main difference is whereas Scala uses patter-matching, Kotlin code relies on when expression.

Scala version also uses Try , which is, although not hard to implement, doesn’t exist in Kotlin. I decided to omit it, as we’ll come back to it later in this article.

The most interesting part here is the InvokeFunction. Note that we have to use star projection for the first type argument, and that we’re doing unsafe cast with as.

Now let’s use the Runtime to output something a bit more meaningful than just “Hello World”:

Outputs:

UUID: <some UUID here>

Our effects system in Kotlin seems to be working! You can see how our simple interpreter unwraps chain of events step by step, outputting the final result.

So, there are no technical limitations in the type system preventing us from implementing it. Can we call it a day, then? Well, not so fast.

Let’s look at the following example next:

This code won’t compile, as Kotlin compiler correctly points out that it.first() may cause NullPointerException. One way to solve it would be of course to use null-safe operation: it?.first()

But would it be possible instead to make our effect system null-safe?

Remember that in Kotlin, when we specify generic as <A>, it actually means <A : Any?>

You can check that by changing definition of KIO to be sealed class KIO<out A : Any?>.

Now it’s enough to change class to be declared as: sealed class KIO<out A : Any> , and the IDE will guide us through all the required changes. Don’t believe it? Here’s a video how easy it is:

So far, so good.

The differences in type systems begin to manifest themselves in the next example, though:

As there’s no easy way to describe Scala’s B >: A in Kotlin, as far as I can tell, we’ll have to resort to start projection again:

What that means is that instead of doing the validation during the compile time, we’ll have to do it in our “interpreter” code, at runtime. Which in turn impacts our type safety.

In the original article, Dmitry is further investigating how such system could be made concurrent, and I would encourage you to read through that. But seeing as my example becomes more and more verbose, I decided not to follow through with that.

Conclusions

The more I progressed with this fun exercise, the more I got convinced, that while it would be possible to build a fully functioning functional effect system in Kotlin, it will be much more verbose, than in Scala.

There are two main reasons for that:

  • Scala’s For Comprehension, that make use of map and flatMap methods, allows for much more concise code
  • Scala’s type system is more expressive than Kotlin

Albeit the commonalities, those two languages are distinct enough, and simply replicating ones approach in another language doesn’t bring much benefit.

Instead, in one of my next articles I’d like to explore what a more idiomatic system based on DSL and coroutines would look like in Kotlin.

Footnotes

If you’re interested in Kotlin, be sure to check my video course: Web Development with Kotlin (for non-Java developers) on Udemy.

The Startup

Get smarter at building your thing. Join The Startup’s +750K followers.