Skip to the content.

Getting started with KIO

KIO is a side effect wrapper that suspends side effects in order to provide referential transparency even when your code needs to interact with the outside world. The KIO data type has three type parameters:

So, basically, you can see KIO<R, E, A> as a wrapper to:

(R) -> Either<E, A>

In order to manage in a simpler way the instances, some useful type aliases are provided:

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>

that basically means:

you can also create your own combination even if there is no a type alias already provided by the library.

How to create a KIO instance

In order to create a KIO instance, some functions are provided:

Some additional notes:

Comprehension-like syntax

With KIO there are two ways to rewrite nested map/flatMap; the first one is throuht the mapT/flatMapT, that get the result from the argument function and put it in a tuple with the provided input parameter:

val io = printIntroductionText()
    .flatMap { retrieveWorldSizeKm() }
    .map { worldSizeKm -> convertKmToMiles(worldSizeKm) }
    .flatMapT { worldSizeMiles -> readInitialPosition(worldSizeMiles) }
    .flatMapT { (_, _) -> readInitialDirection() }
    .flatMap { (size, pos, dir) -> initState(size, pos, dir) }

The second way is cleaner and more readable (since KIO 0.5):

val io = 
    printIntroductionText()                 +
    retrieveWorldSizeKm()                   to  { worldSizeKm ->
    convertKmToMiles(worldSizeKm)           set { worldSizeMiles ->
    readInitialPosition(worldSizeMiles)     to  { pos ->
    readInitialDirection()                  to  { dir ->
    initState(worldSizeMiles, pos, dir)
}}}}

In this code, the to operator is semantically used inject in the context the result of the same-row function call; the same is for the set operator, but it works for pure function calls. Lastly, the + operator is used to concatenate two effectful functions where the output of the first function call is discarted (or it’s Unit).

Accessing the environment

If you are going to use the R parameter, you can retrieve the injected data using the ask function if you are going to instantly wrap a side effect with the provided data, or the askPure function where you optionally can provide a mapping function.

Providing the environment value

In any moment you can provide an instance of the R parameter with the provide method. This will return a KIO instance without R type parameter (IO, UIO, Task, …). In this way, you can also combine instances of KIO with different R types.

Concurrency

KIO doesn’t provide advanced tools for concurrency management. There are only two functions that you can use for this purpose, that are:

Note: in order to use these functions you need to use the RuntimeSuspended version of the runtime.

Executing the program

KIO provides two runtime for program execution: Runtime and RuntimeSuspended. The first one doesn’t support coroutines, suspend functions and therefore the concurrency primitives; RuntimeSuspended, on the other hand, supports all the coroutines goodies but, if you are not already inside a suspend function, it add the overhead of the creation of a new coroutine context in order to execute the code.

When executing the code, you can provide the R instance you are going to inject. Also, for the suspended version, you can provide the CoroutineContext you want to use for the execution.

Optional values

KIO can be used also for modelling an optional value, by simply using the Empty object as type and value for E. Be warned that this is suitable only when you are going to manage deferred computations that could return a value or not. Instead, if you need to describe a data type that can have a value or not inside your data structures, the philosophy of KIO is to don’t use the Optional data type but instead just use nullable types, that in Kotlin are safe to use, unlike Java. Also, the KIO library provides some utility functions in order to easily manage them: