Result pattern with Coroutines

In my previous post, “The result pattern with Kotlin“, I introduced the result pattern, where it fits in the land of Kotlin and whether you should use it in your projects. This post will focus on how to integrate the result pattern with Kotlin coroutines using Result4K.

Kotlin was designed and written in such a way that we can utilise the power of Coroutines in a very familiar and simple way. Because of this, the result pattern, using Result4K, can be wired up quite easily (with caveat).

Lets build a house

Below, you can see the the BuilderService. This service has a single public function called makeABuilding. This function calls off to a number of suspended functions that build specific parts of our building. We want the service to do the following:

  1. Lay the foundations
  2. Build the walls
  3. Asynchronously:
    • Put in the windows
    • put in the doors
    • put on the roof
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
class BuilderService {

fun makeABuilding(): Result<Boolean, Nothing> =
layTheFoundations()
.flatMap { buildTheWalls() }
.flatMap {
zip(
putInTheWindows(),
putInTheDoors(),
putOnTheRoof()
) { _, _, _ -> true }
}

private suspend fun layTheFoundations() = coroutineScope {
delay(300)
Success(true)
}

private suspend fun buildTheWalls() = coroutineScope {
delay(600)
Success(true)
}

private suspend fun putInTheWindows() = coroutineScope {
delay(300)
Success(true)
}

private suspend fun putInTheDoors() = coroutineScope {
delay(300)
Success(true)
}

private suspend fun putOnTheRoof() = coroutineScope {
delay(500)
Success(true)
}
}

This code looks fine, however, putInTheWindows(), putInTheDoors() and putOnTheRoof() are not running asynchronously! By default, suspended functions run sequentially. Simply throwing suspended functions into the zip function available in Result4K does not give us our desired behaviour.

Going asynchronous

We can write our own zipAsync function to mimic that of the Result4K built-in to enable asynchronous execution. We will use Coroutine’s async to produce Deferred results that are awaited on before calling out to the usual synchronous zip.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
class BuilderService {

suspend fun makeABuilding(): Result<Boolean, Nothing> =
layTheFoundations()
.flatMap { buildTheWalls() }
.flatMap {
coroutineScope {
zipAsync(
async { putInTheWindows() },
async { putInTheDoors() },
async { putOnTheRoof() }
) { _, _, _ -> true }
}
}
}

suspend inline fun <T1, T2, T3, U, E> zipAsync(
r1: Deferred<Result<T1, E>>,
r2: Deferred<Result<T2, E>>,
r3: Deferred<Result<T3, E>>,
crossinline transform: (T1, T2, T3) -> U
): Result<U, E> = coroutineScope {
zip(r1.await(), r2.await(), r3.await(), transform)
}

We now get asynchronous resultified zipping! This is a step in the right direction but we are forcing ourselves to conform to adding extra boilerplate each time we call zipAsync. Lets tidy this up a bit.

Make it look pretty

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
class BuilderService {

suspend fun makeABuilding(): Result<Boolean, Nothing> =
layTheFoundations()
.flatMap { buildTheWalls() }
.flatMap {
zipAsync(
{ putInTheWindows() },
{ putInTheDoors() },
{ putOnTheRoof() }
) { _, _, _ -> true }
}
}

suspend inline fun <T1, T2, T3, U, E> zipAsync(
crossinline r1: suspend () -> Result<T1, E>,
crossinline r2: suspend () -> Result<T2, E>,
crossinline r3: suspend () -> Result<T3, E>,
crossinline transform: (T1, T2, T3) -> U
): Result<U, E> = coroutineScope {
val d1 = async { r1() }
val d2 = async { r2() }
val d3 = async { r3() }
zip(d1.await(), d2.await(), d3.await(), transform)
}

Now, we pass our suspended functions into our zipAsync. Much better!
We still achieve our asynchronous execution in our slightly more wordy zipAsync function, that will be hidden away in some utility file never to be seen again, and have cleaned up our call-site!

To have a play with the above examples, head over to the following Git repo