I have something like this in Kotlin, repeated in several places (and please bear in mind that I'm relatively new to the language and I'm still figuring out what's the best/ more idiomatic way to do things):
class SomeClass {
fun someMethod(c: Context) {
val id: String? = c.someValue?.someId
if(id == null) {
return someResult("some message")
}
doSomething(id)
}
}
I would like to find an idiomatic way of extracting
if(id == null) {
return someResult("some message")
}
and still be able to use the value of id without having to help the compiler determining its value is not null. How can I do this idiomatically in Kotlin?
You can use kotlin elvis operator it works the same as if(id == null) {...} :
class SomeClass {
fun someMethod(c: Context) {
val id: String = c.someValue?.someId ?: return someResult("some message")
doSomething(id)
}
}
Consider this nice utility extension function i wanted to use :
inline infix fun <T> T?.otherwise(other: () -> Unit): T? {
if (this != null) return this
other()
return null
}
It could be very useful for logging stuff when expressions evaluated to null for example:
val x: Any? = null
x?.let { doSomeStuff() } otherwise {Log.d(TAG,"Otherwise happened")}
but I see that it wont work for :
val x: Any? = null
x?.otherwise {Log.d(TAG,"Otherwise happened")}
see here for running example
Well when thinking about it i guess that makes sense that if x is null the ? makes the postfix not be executed, but i dont understand why the let in the first example is any different?
Is it possible to fix the utility to be more robust and work without having to have let in the chain?
First, you can simplify the implementation:
inline infix fun <T> T?.otherwise(other: () -> Unit): T? {
if (this == null) { other() }
return this
}
Or
inline infix fun <T> T?.otherwise(other: () -> Unit): T? =
also { if (it == null) other() }
When you do this:
null?.otherwise { println("Otherwise happened") }
?. means "execute if not null", so otherwise is not executed.
What you need to write is:
null otherwise { println("Otherwise happened") }
Note this is very similar to the ?: operator (as Vadik pointed out in the comments):
null ?: println("Otherwise happened")
The difference is that otherwise always returns the value on the left (the same as also), but ?: returns the value on the right when the value on the left is null.
In my opinion, otherwise is confusing, especially as it always returns the left value despite the name. You would be better to use the ?: operator. Or perhaps rename it to something like alsoIfNull.
The let example executes because, when you don't utilize the infix feature, it looks like this:
x?.let {}.otherwise {println("1")}
Notice that it's not ?.otherwise; therefore, it always executes.
So to use otherwise without let, you can omit the ?.
x.otherwise { ... }
x?.let { doSomeStuff() }.otherwise {Log.d(TAG,"Otherwise happened")}
// ⬇️
val value = if (x != null) {
doSomeStuff()
} else {
null
}
value.otherwise {Log.d(TAG,"Otherwise happened")}
x?.otherwise { Log.d(TAG,"Otherwise happened") }
// ⬇️
if (x != null) {
otherwise { Log.d(TAG,"Otherwise happened") }
} else {
null
}
?. means if the value is not null then execute the method and return the result otherwise return null
I have the following functions to simulate the ternary operator for kotlin
fun Boolean.then(action: () -> Unit): Boolean {
if (this)
action.invoke()
return this
}
fun Boolean.otherwise(action: () -> Unit) {
if (!this)
action.invoke()
}
fun <T> Boolean.then(func: () -> T): T? {
if (this)
return func.invoke()
return null
}
fun <T> T?.otherwise(action: () -> T): T {
return this ?: action.invoke()
}
they are supposed to be used like this :
(check).then { doHello() }.otherwise { doWorld() }
val answer = (check).then { "hello" }.otherwise { "world" }
however when I try to assign a value using the above operators like this:
val visibility: Int = (show).then { View.VISIBLE }.alt { View.GONE }
I get an error saying that the required reply was Int but it actually got Unit which means that it called the first version of the methods instead of the second
Other than renaming the methods (when I changed the first two to thenDo and otherwiseDo it worked), can I write the above code in some way so that the compiler will know to call the second version?
I don't think you need both overloads. If you remove the ones that return Unit, then both your lines of code work:
(check).then { doHello() }.otherwise { doWorld() }
val answer = (check).then { "hello" }.otherwise { "world" }
That's because the first line, where the lambdas return Unit, e.g. doHello(), can still use the generic versions of then and otherwise, as they are still considered functions with a return value, namely Unit.
Although I agree with some the comments above: do you really need this? Why not just use if, which is an expression which returns a value (like the ternary operator). See discussion here for more info.
I was surprised today to learn that this take on apparently idiomatic code fails:
class QuickTest {
var nullableThing: Int? = 55
var nullThing: Int? = null
#Test
fun `test let behaviour`() {
nullableThing?.let {
print("Nullable thing was non-null")
nullThing?.apply { print("Never happens") }
} ?: run {
fail("This shouldn't have run")
}
}
}
It happens because, combined with implicit return, nullThing?.apply{...} passes null to the let, and therefore the elvis operator evaluates on null and runs the second block.
This is pretty horrible to detect. Do we have an appropriate alternative beyond conventional if/else without this pitfall?
You could use also instead of let. also will return nullableThing, whereas let will return whatever the lambda returns.
See this article: https://medium.com/#elye.project/mastering-kotlin-standard-functions-run-with-let-also-and-apply-9cd334b0ef84 (point "3. Return this vs. other type").
Your case is a candidate for also thematic. Compare two block actions:
fun <T> T.also(block: (T) -> Unit): T
fun <T, R> T.let(block: (T) -> R): R
nullableThing?.also {
print("Nullable thing was non-null")
nullThing?.apply { println("Never happens") }
} ?: run {
fail("This shouldn't have run")
}
Another idiomatic way is to use when statement
when (nullableThing) {
null ->
print("Nullable thing was non-null")
nullThing?.apply { println("Never happens") }
else -> fail("This shouldn't have run")
}
In Kotlin is there an equivalent to the Swift code below?
if let a = b.val {
} else {
}
You can use the let-function like this:
val a = b?.let {
// If b is not null.
} ?: run {
// If b is null.
}
Note that you need to call the run function only if you need a block of code. You can remove the run-block if you only have a oneliner after the elvis-operator (?:).
Be aware that the run block will be evaluated either if b is null, or if the let-block evaluates to null.
Because of this, you usually want just an if expression.
val a = if (b == null) {
// ...
} else {
// ...
}
In this case, the else-block will only be evaluated if b is not null.
Let's first ensure we understand the semantics of the provided Swift idiom:
if let a = <expr> {
// then-block
}
else {
// else-block
}
It means this: "if the <expr> results in a non-nil optional, enter the then-block with the symbol a bound to the unwrapped value. Otherwise enter the else block.
Especially note that a is bound only within the then-block. In Kotlin you can easily get this by calling
<expr>?.also { a ->
// then-block
}
and you can add an else-block like this:
<expr>?.also { a ->
// then-block
} ?: run {
// else-block
}
This results in the same semantics as the Swift idiom.
My answer is totally a copy cat from the others. However, I cannot understand their expression easily. So I guess it would be nice to provide an more understandable answer.
In swift:
if let a = b.val {
//use "a" as unwrapped
}
else {
}
In Kotlin:
b.val?.let{a ->
//use "a" as unwrapped
} ?: run{
//else case
}
if let statement
Swift if let Optional Binding (so called if-let statement) is used to extract a non-optional value if one exists, or to do nothing if a value is nil.
Swift's if-let statement:
let b: Int? = 50
if let a: Int = b {
print("Good news!")
} else {
print("Equal to 'nil' or not set")
}
/* RESULT: Good news! */
In Kotlin, like in Swift, to avoid crashes caused by trying to access a null value when it’s not expected, a specific syntax (like b.let { } in second example) is provided for properly unwrapping nullable types:
Kotlin's equivalent1 of Swift's if-let statement:
val b: Int? = null
val a = b
if (a != null) {
println("Good news!")
} else {
println("Equal to 'null' or not set")
}
/* RESULT: Equal to 'null' or not set */
Kotlin’s let method, when used in combination with the safe-call operator ?:, provides a concise way to handle nullable expressions.
Kotlin's inline let function and Elvis Operator of Swift's nil coalescing operator:
val b: Int? = null
val a = b.let { nonNullable -> nonNullable } ?: "Equal to 'null' or not set"
println(a)
/* RESULT: Equal to 'null' or not set */
guard let statement
guard-let statement in Swift is simple and powerful. It checks for some condition and if it evaluates to be false, then the else statement executes which normally will exit a method.
Let's explore a Swift's guard-let statement:
let b: Int? = nil
func method() {
guard let a: Int = b else {
print("Equal to 'nil' or not set")
return
}
print("Good news!")
}
method()
/* RESULT: Equal to 'nil' or not set */
Kotlin's similar effect of Swift's guard-let statement:
Unlike Swift, in Kotlin, there is no guard statement at all. However, you can use the Elvis Operator – ?: for getting a similar effect.
val b: Int? = 50
fun method() {
val a = b ?: return println("Equal to 'null' or not set")
return println("Good news!")
}
method()
/* RESULT: Good news! */
there are two answers above, both got a lot acceptances:
str?.let{ } ?: run { }
str?.also{ } ?: run { }
Both seem to work in most of the usages, but #1 would fail in the following test:
#2 seems better.
Unlike Swift, Its not necessary to unwrap the optional before using it in Kotlin. We could just check if the value is non null and the compiler tracks the information about the check you performed and allows to use it as unwrapped.
In Swift:
if let a = b.val {
//use "a" as unwrapped
} else {
}
In Kotlin:
if b.val != null {
//use "b.val" as unwrapped
} else {
}
Refer Documentation: (null-safety) for more such use cases
Here's how to only execute code when name is not null:
var name: String? = null
name?.let { nameUnwrapp ->
println(nameUnwrapp) // not printed because name was null
}
name = "Alex"
name?.let { nameUnwrapp ->
println(nameUnwrapp) // printed "Alex"
}
Here's my variant, limited to the very common "if not null" case.
First of all, define this somewhere:
inline fun <T> ifNotNull(obj: T?, block: (T) -> Unit) {
if (obj != null) {
block(obj)
}
}
It should probably be internal, to avoid conflicts.
Now, convert this Swift code:
if let item = obj.item {
doSomething(item)
}
To this Kotlin code:
ifNotNull(obj.item) { item ->
doSomething(item)
}
Note that as always with blocks in Kotlin, you can drop the argument and use it:
ifNotNull(obj.item) {
doSomething(it)
}
But if the block is more than 1-2 lines, it's probably best to be explicit.
This is as similar to Swift as I could find.
There is a similar way in kotlin to achieve Swift's style if-let
if (val a = b) {
a.doFirst()
a.doSecond()
}
You can also assigned multiple nullable values
if (val name = nullableName, val age = nullableAge) {
doSomething(name, age)
}
This kind of approach will be more suitable if the nullable values is used for more than 1 times. In my opinion, it helps from the performance aspect because the nullable value will be checked only once.
source: Kotlin Discussion
I'm adding this answer to clarify the accepted answer because it's too big for a comment.
The general pattern here is that you can use any combination of the Scope Functions available in Kotlin separated by the Elvis Operator like this:
<nullable>?.<scope function> {
// code if not null
} :? <scope function> {
// code if null
}
For example:
val gradedStudent = student?.apply {
grade = newGrade
} :? with(newGrade) {
Student().apply { grade = newGrade }
}
The cleanest option in my opinion is this
Swift:
if let a = b.val {
} else {
}
Kotlin
b.val.also { a ->
} ?: run {
}
Swift if let statement in Kotlin
The short answer is use simple IF-ELSE as by the time of this comment there is no equivalent in Kotlin LET,
if(A.isNull()){
// A is null
}else{
// A is not null
}
we can get the same Unwraping syntax like Swift if let using inline fun
inline fun <T:Any?> T?.unwrap(callback: (T)-> Unit) : Boolean {
return if (this != null) {
this?.let(callback)
true
}else {
false
}
}
Uses: :
val name : String? = null
val rollNo : String? = ""
var namesList: ArrayList<String>? = null
if (name.unwrap { name ->
Log.i("Dhiru", "Name have value on it $name")
})else if ( rollNo.unwrap {
Log.i("Dhiru","Roll have value on it")
}) else if (namesList.unwrap { namesList ->
Log.i("Dhiru","This is Called when names list have value ")
}) {
Log.i("Dhiru","No Field have value on it ")
}
The problem with the Any?.let {} ?: run {} constructions is that:
It only allows for one non-null check per statement
If the let block returns null the run block is evaluated anyway
It's not possible to perform multiple checks in a switch/when style
A solution which tackles most of these problems is to define functions like the following:
private inline fun <A> ifNotNull(p1: A?, block: (A) -> Unit): Unit? {
if (p1 != null) {
return block.invoke(p1)
}
return null
}
private inline fun <A, B> ifNotNull(p1: A?, p2: B?, block: (A, B) -> Unit): Unit? {
if (p1 != null && p2 != null) {
return block.invoke(p1, p2)
}
return null
}
private inline fun <A, B, C> ifNotNull(p1: A?, p2: B?, p3: C?, block: (A, B, C) -> Unit): Unit? {
if (p1 != null && p2 != null && p3 != null) {
return block.invoke(p1, p2, p3)
}
return null
}
This would allow for a statement like:
ifNotNull(a, b) { a, b ->
// code when a, b are not null
} ?:
ifNotNull(c) { c ->
// code when a, b are null and c not null
} ?:
ifNotNull(d, e, f) { d, e, f ->
// code when a, b, c are null and d, e, f not null
} ?: run {
// code which should be performed if a, b, c, d, e and f are null
}
The only caveat is that continue and break statements are not supported if executed within a loop compared to Swift's if let equivalent.
Probably I am very late however the easiest way to unwrap and option is
yourOptionalString ?: return
this was all the following lines will have unwrapped string
If b is a member variable then this approach seems most readable to me:
val b = this.b
if (b == null) {
return
}
println("non nullable : ${b}")
This is also consistent with how it works in swift, where a new local variable shadows the member variable.