Add "if" method to boolean type (Early return alternative)

Instead of writting:

fun foo() {
    if(!bar) return // early return

    // perform operations
}

It would be nice to have a convenient method like:

fun foo() = bar.if { /* executed only if bar is true */ }

We can already do that for nulls:

fun foo() = bar?.let { /* executed only if bar is non-null */ }
2 Likes

How is bar.if { ... } better than if (bar) { ... }, other than that it saves a single character?

2 Likes

It enforces the quick return mantra when your method code should only run after one guard clause.
if (bar) { … }
creates a unnecessary indentation.

1 Like

I don’t think I follow. bar.if { ... } creates exactly as much indentation as if (bar) { ... }. Both can be used as an expression body of a method.

  1. You can say exactly the same about ?.let. You could just write if(bar != null) {...}

  2. You seldom write:

    fun foo() {
    if(bar) {
    …
    }
    }

Instead, you write an early return statement:

fun foo() {
    if(!bar) return

   // stuff
}

This proposal would simplify it even further. It improves readability, maintenance and keeps syntax neat.

The main advantage of let is that it allows you to avoid repeating a longer expression. if (x.foo.bar.baz != null) { x.foo.bar.baz.doSomething() becomes x.foo.bar.baz?.let { it.doSomething() }. Your proposed function has no such benefit.

I don’t see at all why, if you prefer to use early return statements to if blocks surrounding the entire function, you’d suddenly stop using early return statements if if becomes a method instead. The advantages and disadvantages of early returns are not affected in any way by the shape of the if condition.

And finally, if you like this style and want to use it in your project, you’re welcome to do so: it takes exactly one line of code to define an extension function Boolean.ifTrue.

1 Like

Could you elaborate what is the signature of the proposed function?

Hi Milosz,

what you are suggesting is the way things are done in Smalltalk. There are methods named ifTrue: / ifFalse: in class True and False, which are subclasses of Boolean. They have a single instance true and false. Let’s hava a look at some Java-ish Smalltalk expression that evaluates to a boolean:

[System.currentTimeInMillis % 2 == 0]
ifTrue: [println(“even”)]
ifFalse: [println(“uneven”)]

Closures in Smalltalk are surrounded by square brackets. So for the example above code not to fail at runtime there needs to be a method in class Closure named ifTrue:ifFalse:

In Smalltalk there is no for loop. Looping is done through collection iterators. 36 years after Smalltalk-80 Java also has them and they are called streams and they are really ugly. Better stick to Groovy/Kotlin/Scala/Javaslang. Really ;-). Other than that for looping over an integer range there is a method to:do:, for example:

1 to: 12 do: [ :index | Transcript cr; show: i printString]

This time no Java-ish Smalltalk code but some real one. So the message to:do: is sent to the object 1 with the first parameter 12 and the second parameter being a unary closure (closure with one parameter). For this to work the method to:do: needs to be put into class Integer.

This way, in Smalltalk, neither conditional expressions nor loops are part of the language. They are implemented as methods in the applicable classes where the receiver object is instantiated from.

When Java came along it basically killed Smalltalk, which had a period of success inside banks and insurance companies (because IBM back then promoted Smalltalk). In Java loops and conditional expressions where keywords of the language and built into the compiler. Advantage being efficiency and better performance compared to dynamic method dispatch as in Smalltalk.

Since Kotlin already has conditionals and loops as part of the language there is little point in providing them again some other way. People are not used to the way things are done in Smalltalk. It confuses them a lot when they see things being done that way. They want things to be done as in the “traditional” way. Another question is whether there is a closure class in Kotlin you can add methods to through extension methods. I guess there is. But the whole thing would appear awkward to most people and especially the developers reading your code.

1 Like

For sure having bar.if { ... } is redundant, but it gives a lot to readability in case of longer stream-like function chains that terminates with a Boolean result (eg. any(), all())
someStream.map().filter().any().if {...} is much more readable than if you have to wrap whole expression into if.

Of course there have to be limits to the wish-list, but the proposal is ok in itself :wink:

2 Likes

I would disagree. I think most people are used to look for the condition after the if, making your example less readable. Also there is a problem with the name. if is a keyword and can therefor not be used as a function name. Normally you could just add a function like this to the std lib but in this case you would need to create a new language construct allowing the name if to be used.

Of course name if would be confusing. Smalltalk like ifTrue would be much better.
I believe that more and more people are getting used to functional style of programming and this kind of constructs (eg. Java Optional) are getting in popularity.

2 Likes

I would not mind adding ifTrue and ifFalse to the std lib.

5 Likes

you can create an extension

fun Boolean.If(action: () → Unit): Boolean {
if (this) action()
return this
}

fun Boolean.Else(action: () → Unit): Boolean {
if (!this) else action()
return this
}

but there is still no much saved really

“If” and “Else” are ambiguous function names.

inline fun Boolean.yes(block: () -> Unit) = also { if (it) block() }

inline fun Boolean.no(block: () -> Unit) = also { if (! it) block() }

also this syntax is useful for null checks
bar?.ifTrue { … }

1 Like

With the standard library:

val condition = true
condition.takeUnless { false }?.let { println(it) }
condition.takeIf { true }?.let { println(it) }
1 Like
fun <T>Boolean.ifTrue(supplier: () -> T) = if (this) supplier() else null

(And yes, this should be included)

If you declare that function as inline, then it can be used for non-local returns also.

I would also allow nullable boolean as in:

inline fun <T>Boolean?.ifTrue(supplier: () -> T) = if (this == true) supplier() else null

So you can do:

return functionReturningNullableBoolean().ifTrue { "yes" } ?: "no"

You don’t need to declare the boolean as nullable to achieve that feature:

inline fun <T>Boolean.ifTrue(supplier: () -> T) = if (this == true) supplier() else null

val bool1: Boolean = Todo()
val s1 = bool1.ifTrue { "yes" } ?: "no"

val bool2: Boolean? = Todo()
val s2 = bool2?.ifTrue { "yes" } ?: "no"

This gives you a bit more control over how nullable argument should be handled.