Introducing KIO

Functional programming with Kotlin is sometimes a very delightful experience. But other times, it can be very painful. This is because the Kotlin language sports many features that are very aligned with the functional programming paradigm an so using them becomes very easy. But when trying to follow the same approach that we use with Scala or Haskell, with typeclasses, high order kinds, tagless final and so on, we quickly hit the language limitations. Some libraries, like Arrow, are trying to work around these limitations with some tricks and, in the close future, compiler plugins.

Looking around how different programming languages fulfil the FP needs, I found out that some of them like F# or clojure doesn’t follow the same approach coming from the Haskell world, but instead they just try to stick with the fundamental characteristics of a functional program. Just as an example, the book Domain Modeling Made Functional that explores the functional approach for a business domain application, using F#, doesn’t even mention things like Semigroup, Comonad and so on… It just sticks with the fundamentals. So, I tried to follow the same approach with Kotlin.

What tools do I need?

To me, the fundamental rule for functional programming is the referential transparency, that basically means that you should be able to replace a variable with its definition, or viceversa, and the output of the program (both in terms of result and side effects) shouldn’t change. If you think about it, every other FP requirement comes out from this, like immutability, side effect handling and so on. Other than this, the other important big player in this game is the algebraic data types encoding. If you get these two pillars, you should be able to code entire applications using the FP paradigm.

What Kotlin provides

The Kotlin programming language provides us many tools, let’s summarise most of them here:

  • immutability thanks to the val keyword;
  • product types with data classes and the copy method;
  • sum types with sealed classes;
  • first class functions and high order functions;
  • top-level and generic functions;
  • type-safe optional values with the nullable data types and related operators;
  • tail call optimisation for recursive functions.

What Kotlin doesn’t provide

Given the above list, we are missing basically two things:

  • an easy way to handle and defer side effects (problem usually solved with the IO monad);
  • an alternative way of error handling in substitution to the exceptions throwing/handling.

In order to take care of these two missing pieces, I wrote a very little library: KIO.

What is KIO

KIO is a standalone and lightweight implementation of the IO monad, inspired to the ZIO library for Scala. It basically models a function like:

(R) -> Either<E, A>

Given an input of type R (like what is done with the Reader monad), it provides a result that could be either an error of type E or a value of type A. This is how the KIO type behave:

KIO<R, E, A>

Given this definition, you can also define some derived data types. Directly from the KIO source code:

typealias IO<E, A> = KIO<Any, E, A>
typealias URIO<R, A> = KIO<R, Nothing, A>
typealias UIO<A> = URIO<Any, A>
typealias Task<A> = IO<Throwable, A>
typealias RIO<R, A> = KIO<R, Throwable, A>
typealias Option<A> = IO<Empty, A>

that basically means:

  • IO: an instance of KIO where there is no R injected;
  • UIO: a computation that can’t fail (or at least that isn’t typed for failure);
  • URIO: like UIO, but with the injection of type R;
  • Task: an instance of KIO without R injection and where the error type is set to Throwable;
  • RIO: like Task, but with R injection;
  • Option: an instance of KIO, without R injection, where the error type is limited to only one value that is Empty;
  • you can also create your own combination even if there is no a typealias already provided by the library.

So, with KIO, you can handle side effects like a standard IO monad; also, you can manage errors both typing them as Throwable or with some domain specific sum types. These are the two Kotlin’s missing pieces that we’ve pointed out in the previous paragraph; so with KIO we have completed all the pieces of our puzzle.

In addition, the R injection provides a good way to manage dependency injection, as R could represent both data or functions; we could for example inject some modules (e.g. logging, database access, configuration, …) in the same way we’ve seen here in the previous posts.

A simple example

Here is a simple example of usage of KIO:

import it.msec.kio.*
import it.msec.kio.result.getOrNull
import it.msec.kio.runtime.Runtime.unsafeRunSync

data class Account(val username: String)

fun unsafeRetrieveAccountFromDB(id: String): Account =
        throw IllegalArgumentException("No account found with id = $id")

fun safeRetrieveAccountFromDB(userId: String): Task<Account> =
        unsafe { unsafeRetrieveAccountFromDB(userId) }

fun printToConsole(s: String): Task<Unit> =
        unsafe { println(s) }

fun readFromConsole(s: String): Task<String?> =
        printToConsole(s).flatMap { unsafe { readLine() } }

fun retrieveUsername(userId: String): Task<String> =
        safeRetrieveAccountFromDB(userId)
                .map { it.username }
                .peekError { e -> printToConsole("Warning: ${e.message}") }
                .recover { "anonymous" }

fun interactOnConsole(username: String): Task<String> =
        printToConsole("Welcome $username")
                .flatMap { readFromConsole("Enter some text:") }
                .map { it.orEmpty() }

fun main() {
    val userId = "123"
    val option: Option<String> = retrieveUsername(userId)
            .flatMap(::interactOnConsole)
            .toOption()

    val userInput = unsafeRunSync(option).getOrNull()
    println(userInput)
}

Should I use KIO?

Currently KIO is just a personal project of mine, even if I expect it to be production ready. Also, it must be very clear that KIO follows the approach I described in the introduction, so I’m not going to create another Arrow (that is a very complete and useful library) but just to provide the missing tools that can enable us to experiment with the Kotlin FP fundamentals without depending on a big library like Arrow that, right now, is still under development. Maybe in the future, with the release of Arrow 1.x or with some new Kotlin language features, KIO will become worthless. Right now, with my current FP experience, this is the solution that I trust more in order to write some FP code that I expect to deploy in production.

Where is KIO?

You can find KIO on github here, both source code and maven repository instructions.

Ok, but the documentation?

Currently the only documentation provided are the unit and integration tests and the source code itself. But I’m going to work on it in order to provide some guidance on using KIO and all it provides. Stay tuned!

UPDATE 07/10: the page has been updated with the changes of the 0.3 release that is now available.