This content originally appeared on DEV Community and was authored by Victor
As always, TL;DR at the end.
The title of this post is a bit misleading. I do use the Elvis operator in Kotlin, it is a shame that Java doesn't have it (along with a truckload of other features). But I prefer not to use it directly in my code. I'll explain why.
Just to make sure we are on the same page, the Elvis operator is the ?:
operator in Kotlin. It is used to provide a default value when a nullable value is null. For example:
val someNullableValue: String? = null
val defaultValue: String = "default"
// Not nullable anymore
val result: String = someNullableValue ?: defaultValue
"Piping" of code
Kotlin made the decisions made on Java 8 a design principle. Java 8 brought, along with other features, the Optional and Stream classes. These classes are designed to isolate the internal context of the objects contained (nullability, quantity) from the outside world. This is called a Monad, and it is a very powerful concept, in which some languages are entirely based on, while Kotlin allows you to use it when you want to.
One of the common operations with Monads is the "piping" of code. This is when you chain a series of operations on the same object, like this:
val result = someObject
.operation1()
.operation2()
.operation3()
// Or more concretely
val result: Optional<Any> = someOptional
.map { operation1(it) }
.flatMap { operation2ReturningOptional(it) }
.filter { operation3ReturningBoolean(it) }
In Kotlin, although not as direct as with Optionals, you have exactly the same concept with nullable types. You can chain operations on nullable types using ?.
, and thus achieving the same effect as the map, flatMap and filter functions of the Optional class.
val result = someObject
?.let { operation1(it) }
?.let { operation2(it) }
?.takeIf { operation3(it) }
Each operation mantains the nullability of the object, and thus achieving the optionality that the Optional class provides. The main difference is that, even after you drop the nullability of something (with, say, the elvis operator), you can still pipe the code further, with the same let
, run
, apply
, also
functions as before. And here is where the Elvis operator fails.
Piping is even further pushed by kotlin via the expression functions. If a function is simple and has does as little logic as it can (as most functions should), you can write them as expressions:
suspend fun saveUser(userVO: UserVO) =
userVO
.let(::toEntity)
.let(userRepository::save)
The Elvis operator
The failure of the elvis operator comes from the fact that it isn't chainable, as it like an infix
function. This means that you can't use it in the middle of a chain of operations, like you can with the ?.
operator. This is a problem because it breaks the flow of the code, and makes it harder to read and understand.
val result = someObject
?.takeIf { operation3(it) }
?: defaultValue
// Can't chain further
There's two ways to mitigate this. The first one is to use the let
function, which is a bit more verbose:
val result = someObject
?.takeIf { operation3(it) }
.let { it ?: defaultValue }
And the second one is to add parentheses to the elvis operator, which is even worse, specially in long chains:
val result = (
someObject
?.let { operation(it) }
?.takeIf { operation3(it) }
?: defaultValue
)
.let { operation4(it) }
Depending on your linter, this could look atrocious. So how could you use it?
The solution
When I start a Kotlin project, I usually add a couple of must have
extensions, some of which I don't understand why they aren't in the standard library. One of them is the orElse
extension function:
inline fun <T> T?.orElse(block: () -> T): T = this ?: block()
As it is an inline function, it doesn't add any overhead to the code, and it is very easy to use:
val result = someObject
?.takeIf { operation3(it) }
.orElse { defaultValue }
.let { operation4(it) }
And, as it is itself a function call, you can chain it as much as you want, thus solving the problem of the Elvis operator. Again, I don't really know why this isn't in the standard library, but it is a very useful function to have.
Conclusion & TL;DR
The Elvis operator is a very useful operator in Kotlin, but it breaks the flow of the code when used in the middle of a chain of operations. To solve this, you can use the orElse
extension function, which is a very simple function that provides the same functionality as the Elvis operator, but in a chainable way.
inline fun <T> T?.orElse(block: () -> T): T = this ?: block()
This content originally appeared on DEV Community and was authored by Victor
Victor | Sciencx (2024-08-10T19:00:54+00:00) Kotlin Elvis (?:) Operator: why I don’t use it. Retrieved from https://www.scien.cx/2024/08/10/kotlin-elvis-operator-why-i-dont-use-it/
Please log in to upload a file.
There are no updates yet.
Click the Upload button above to add an update.