在 < ,< = ,> = ,> 比较中使用 Kotlin WHERE 子句

我试图使用 WHEN子句与 ><进行比较。

这个不能编译。有没有一种方法可以在比较中使用普通的布尔运算符集(< < = ,> = >)来启用这个功能?

val foo = 2


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

我试图找到一个无限范围的比较,但是也不能使这个工作吗?有没有可能把它写成一个无界的范围?

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

你可以把整个表达式放在第二部分,这是可以的,但似乎是不必要的重复。至少它可以编译和工作。

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

但我不确定这是否比我们多年来一直在做的“如果-否则”(if-else)选择更简单。比如:

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

当然,现实世界中的问题比上述问题更复杂,WHEN子句很有吸引力,但是不能像我期望的那样用于这种类型的比较。

35736 次浏览

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...

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.

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.

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.

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

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!

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