Functional kotlin part 2: elvis operator

Continuing series of posts #kotlin-showoff about functional constructions in kotlin I want to demostrate use of elvis operator

Essentially, elvis operator lvalue ?: rexpression is returning left value if it's not null or executes rexpression otherwise. The crazy thing about kotlin is most of the constructions are expressions and that gives another way to express business logic.

Returning alternative value

In the simplest case we can return alternative value if left value is null, for example

val userList = cache.get(userId) ?: fetch(userId)

Literally means: get value of cache.get(userId) if it's not null or fetch(userId)

In Java it can be expressed as

List<User> userList = cache.get(userId);
if (userList == null) userList = fetch(userId);

Also, it's possible to chain elvis operators

val userList = memoryCache.get(userId) 
     ?: diskCache.get(userId)
     ?: fetch(userId)

Looks lovely and clean, isn't it?

Early return with elvis

fun displayUsers() {
    val userList = cache.get(userId) 
        ?: return
    updateUiWithNewUserList(userList)
}

In this case return if userList is null function will return immideatelly

Throw exception with elvis operator

In Java code we often need to manage an exceptional situation and allow callers to handle it themselves. Take a look on this example:

void displayUsers() {
    List<User> userList = cache.get(userId);
    if (userList == null) 
        throw new NoDataAvailableException()
    . . . 
}

Employing elvis operator in kotlin it looks more concise and logic is encapsulated within one expression:

fun displayUsers() {
    val userList = cache.get(userId) 
        ?: throw NoDataAvailableException()
    . . .
}

Looks cleaner, isn't it?

Elvis operator with in loops

In the same way as we used early return it's possible to make elvis to work with break and continue, in case if you're still using loops

fun top10Score(users: List<User>): Int {
    var totalScore = 0
    for(i in 0 until Math.min(users.size, 10)) {
        val score = users[i].gameProgress?.score 
            ?: continue
        totalScore += score
    }
    return totalScore
}

If either gameProgress or score are null loop will continue to the next element.
By the way, there is a way expressive functional way to implement the same logic:

fun top10Score(users: List<User>): Int = users
    .take(10)
    .fold(0) { acc, it -> acc + (it.gameProgress?.score ?: 0) }

It works not only for nullable types

Kotlin has numerous extension functions allowing to express flow in the concise and functional manner. We'll dive into this topic in the following articles.

But here is a spoiler for you, say it's need to throw exception in validator when user didn't provide name or it was empty. From data point of view null isn't same as empty string, empty string isn't same as a string full of spaces. But in our business logic it's practically the same since no name is provided. Here is an example of handling this situation:

val name = user.name?.takeIf(String::isNotBlank)
    ?: throw UserNameIsEmptyException()

takeIf returns receiver object if predicate is true. Even user.name is not null but consist of empty string takeIf will return null because it doesn't satisfy condition. Exception will be thrown in this case and it's guaranteed by compiler that name is not null

Conclusion

Elvis operator in kotlin is very powerful and helps to handle conditional logic naturally within the data flow