Jim Leask
Jim Leask

Reputation: 6429

Using Kotlin WHEN clause for <, <=, >=, > comparisons

I'm trying to use the WHEN clause with a > or < comparison.

This doesn't compile. Is there a way of using the normal set of boolean operators (< <=, >= >) in a comparison to enable this?

val foo = 2

// doesn't compile
when (foo) {
    > 0 -> doSomethingWhenPositive()
    0   -> doSomethingWhenZero()
    < 0 -> doSomethingWhenNegative()
}

I tried to find an unbounded range comparison, but couldn't make this work either? Is it possible to write this as an unbounded range?

// trying to get an unbounded range - doesn't compile
when (foo) {
    in 1.. -> doSomethingWhenPositive()
    else -> doSomethingElse()
}

You can put the whole expression in the second part, which is OK but seems like unnecessary duplication. At least it compiles and works.

when {
    foo > 0 -> doSomethingWhenPositive()
    foo < 0 -> doSomethingWhenNegative()
    else -> doSomethingWhenZero()
}

But I'm not sure that is any simpler than the if-else alternative we have been doing for years. Something like:

if ( foo > 0 ) {
    doSomethingWhenPositive()
}
else if (foo < 0) {
    doSomethingWhenNegative()
}
else {
    doSomethingWhenZero()
}

Of course, real world problems are more complex than the above, and the WHEN clause is attractive but doesn't work as I expect for this type of comparison.

Upvotes: 120

Views: 65920

Answers (7)

Frankenzilla
Frankenzilla

Reputation: 39

Mo code that works:

val fishMan = "trouttrout"

when (fishMan.length){
    0 -> println("Error")
    in (3..12) -> println("Good fish name")
    else -> println ("OK fish name")
}

Result: Good fish name

Upvotes: 3

user2297550
user2297550

Reputation: 3346

I use this:

val foo = 2

when (min(1, max(-1, foo))) {
    +1 -> doSomethingWhenPositive()
     0 -> doSomethingWhenZero()
    -1 -> doSomethingWhenNegative()
}

The imports needed for this case are:

import java.lang.Integer.min
import java.lang.Integer.max

but they can be generalized to other types.

You're welcome!

Upvotes: -1

Naveen T P
Naveen T P

Reputation: 7275

We can use let to achieve this behaviour.

response.code().let {
    when {
        it == 200 -> handleSuccess()
        it == 401 -> handleUnauthorisedError()
        it >= 500 -> handleInternalServerError()
        else -> handleOtherErrors()
    }
}

Hope this helps

Upvotes: 26

kyay10
kyay10

Reputation: 942

I found a bit hacky way that can help you in mixing greater than, less than, or any other expression with other in expressions. Simply, a when statement in Kotlin looks at the "case", and if it is a range, it sees if the variable is in that range, but if it isn't, it looks to see if the case is of the same type of the variable, and if it isn't, you get a syntax error. So, to get around this, you could do something like this:

when (foo) {
    if(foo > 0) foo else 5 /*or any other out-of-range value*/ -> doSomethingWhenPositive()
    in -10000..0   -> doSomethingWhenBetweenNegativeTenKAndZero()
    if(foo < -10000) foo else -11000 -> doSomethingWhenNegative()
}

As you can see, this takes advantage of the fact that everything in Kotlin is an expression. So, IMO, this is a pretty good solution for now until this feature gets added to the language.

Upvotes: -4

ice1000
ice1000

Reputation: 6569

You want your code to be elegant, so why stay on the when expression. Kotlin is flexible enough to build a new one using extension.

First we should claim that we can only pass a Comparable<T> here because you have to compare the value.

Then, we have our framework:

fun <T: Comparable<T>> case(target: T, tester: Tester<T>.() -> Unit) {
    val test = Tester(target)
    test.tester()
    test.funFiltered?.invoke() ?: return
}
class Tester<T : Comparable<T>>(val it: T) {
    var funFiltered: (() -> Unit)? = null
    infix operator fun Boolean.minus(block: () -> Unit) {
        if (this && funFiltered == null) funFiltered = block
    }

    fun lt(arg: T) = it < arg
    fun gt(arg: T) = it > arg
    fun ge(arg: T) = it >= arg
    fun le(arg: T) = it <= arg
    fun eq(arg: T) = it == arg
    fun ne(arg: T) = it != arg
    fun inside(arg: Collection<T>) = it in arg
    fun inside(arg: String) = it as String in arg
    fun outside(arg: Collection<T>) = it !in arg
    fun outside(arg: String) = it as String !in arg
}

After that we can have elegant code like:

case("g") {
    (it is String) - { println("hello") } // normal comparison, like `is`
    outside("gg") - { println("gg again") } // invoking the contains method
}

case(233) {
    lt(500) - { println("less than 500!") }
    // etc.
}

If you're happy, you can rename the minus function to compareTo and return 0. In such way, you can replace the - with =>, which looks like scala.

Upvotes: 7

Lior Bar-On
Lior Bar-On

Reputation: 11480

Even a flexible language such as Kotlin doesn't have a "elegant" / DRY solution for each and every case.

You can write something like:

when (foo) {
    in 0 .. Int.MAX_VALUE -> doSomethingWhenPositive()
    0    -> doSomethingWhenZero()
    else -> doSomethingWhenNegative()
}

But then you depend on the variable type.

I believe the following form is the most idiomatic in Kotlin:

when {
    foo > 0  -> doSomethingWhenPositive()
    foo == 0 -> doSomethingWhenZero()
    else     -> doSomethingWhenNegative()
}

Yeah... there is some (minimal) code duplication.

Some languages (Ruby?!) tried to provide an uber-elegant form for any case - but there is a tradeoff: the language becomes more complex and more difficult for a programmer to know end-to-end.

My 2 cents...

Upvotes: 140

Pixel Elephant
Pixel Elephant

Reputation: 21383

The grammar for a when condition is as follows:

whenCondition (used by whenEntry)
  : expression
  : ("in" | "!in") expression
  : ("is" | "!is") type
  ;

This means that you can only use is or in as special cases that do not have to be a full expression; everything else must be a normal expression. Since > 0 is not a valid expression this will not compile.

Furthermore, ranges are closed in Kotlin, so you cannot get away with trying to use an unbounded range.

Instead you should use the when statement with a full expression, as in your example:

when {
    foo > 0 -> doSomethingWhenPositive()
    foo < 0 -> doSomethingWhenNegative()
    else -> doSomethingWhenZero()
}

Or alternatively:

when {
    foo < 0 -> doSomethingWhenNegative()
    foo == 0 -> doSomethingWhenZero()        
    foo > 0 -> doSomethingWhenPositive()        
}

which may be more readable.

Upvotes: 14

Related Questions