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

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.

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

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

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.

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

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!

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.

Related

What is the syntax when using number comparations in when statement

I have this code:
statisticsSettings = when (ScreenHandler.convertPixelsToDp(width, context).toInt()){
320 -> StatisticsSettings.SMALL_PHONE
480 -> StatisticsSettings.LARGE_PHONE
600 -> StatisticsSettings.SMALL_TABLET
720 -> StatisticsSettings.LARGE_TABLET
else -> throw IllegalArgumentException("Cannot compute dp")
}
and I was wondering if I could make cases of the when statement with a comparator instead of an integer. Something like this:
statisticsSettings = when (ScreenHandler.convertPixelsToDp(width, context).toInt()){
ScreenHandler.convertPixelsToDp(width, context).toInt()) < 320 -> StatisticsSettings.SMALL_PHONE
ScreenHandler.convertPixelsToDp(width, context).toInt()) < 480 -> StatisticsSettings.LARGE_PHONE
ScreenHandler.convertPixelsToDp(width, context).toInt()) < 600 -> StatisticsSettings.SMALL_TABLET
ScreenHandler.convertPixelsToDp(width, context).toInt()) < 720 -> StatisticsSettings.LARGE_TABLET
else -> throw IllegalArgumentException("Cannot compute dp")
}
Use ranges:
val statisticsSettings = when (ScreenHandler.convertPixelsToDp(width, context).toInt()){
in 0..320 -> StatisticsSettings.SMALL_PHONE
in 321..480 -> StatisticsSettings.LARGE_PHONE
in 481..600 -> StatisticsSettings.SMALL_TABLET
in 601..720 -> StatisticsSettings.LARGE_TABLET
else -> throw IllegalArgumentException("Cannot compute dp")
}
Or you could use enum constants:
enum class StatisticsSettings(val intRange: IntRange) {
SMALL_PHONE(0..320),
LARGE_PHONE(321..480),
SMALL_TABLET(481..600),
LARGE_TABLET(601..720)
}
val intRange = ScreenHandler.convertPixelsToDp(width, context).toInt()
val statisticsSettings = StatisticsSettings.values().find { intRange in it.intRange }
This has the advantage that the ranges are "bound" to the enum itself. If you ever change these values, you don't have to change them on possibly multiple locations in your code.
Edit: switched from filter to find (thanks to #ArpitShukla, see comment below)

How to handle "-> empty" in Kotlins "when"

Lets assume the following when-statement:
when(a)
{
x -> doNothing()
y -> doSomething()
else -> doSomethingElse()
}
Now i'm looking to eliminate the boilerplate-function "doNothing()", e.g.:
x -> //doesn't compile
x -> null //Android Studio warning: Expression is unused
x -> {} //does work, but my corporate codestyle places each '{‘ in a new line, looking terrible
//also, what is this actually doing?
Any better ideas?
I can't just eliminate x -> completely, as that would lead to else -> doSthElse()
Edit: directly after writing this Question, i figured out a possible answer x -> Unit. Any shortcomings with that?
Kotlin has two existing possibilities to express a "do nothing" construct in when statements. Either Unit or an empty pair of braces. An empty block will just execute nothing.
There's nothing else planned in that regard (see here).
To answer your question regarding "also, what is this actually doing?" for the empty block, looking at the bytecode and translating it into Java helps:
val x = 33
when(x)
{
1 -> {}
2 -> Int
3 -> Unit
else -> Double
}
Translates to
int x = 33;
switch(x) {
case 1:
case 3:
break;
case 2:
IntCompanionObject var10000 = IntCompanionObject.INSTANCE;
break;
default:
DoubleCompanionObject var1 = DoubleCompanionObject.INSTANCE;
}

Convert from char to operator Kotlin

Is there any way to convert a char, lets say with a value of '+', into the operator +? Something like this:
println(1 charOperator 1);
output:
2
You can use something like this:
fun operatorFromChar(charOperator: Char):(Int, Int)->Int
{
return when(charOperator)
{
'+'->{a,b->a+b}
'-'->{a,b->a-b}
'/'->{a,b->a/b}
'*'->{a,b->a*b}
else -> throw Exception("That's not a supported operator")
}
}
and later call:
println(operatorFromChar('+').invoke(1,1))
Operators are, at the end of the way, functions. If you return a function with the operator's job, you can invoke it as it was the operator itself, but it will never be as "pretty" as calling the operator directly.
This isn't really possible. Maybe you should add your current solution and there's another way to help you out.
Here's a sneaky solution for calculating expressions with + and - only:
val exp = "10+44-12+3"
val result = exp.replace("-", "+-").split("+").sumBy { it.toInt() }
You can do something like
infix fun Int.`_`(that: Int) = this + that
where the backtick is unnecessary to this character but maybe necessary for other character. Then you can try:
println(2 _ 3) // output: 5
Update according to the comment:
I mean something like
val expr = input.split(' ')
when (expr[1])
{
'+' -> return expr[0].toInt() + expr[2].toInt()
'-' -> return expr[0].toInt() - expr[2].toInt()
'*' -> return expr[0].toInt() * expr[2].toInt()
'/' -> return expr[0].toInt() / expr[2].toInt()
// add more branches
}
However, I was wondering whether there is a better and tricky solution from the grammar of Kotlin.
What you basically want is an Char to result of an operation mapping. So, I decided to return the result right away and not a lambda.
fun Int.doOperation(charOperator: Char, x: Int) = when(charOperator) {
'+' -> this + x
'-' -> this - x
'/' -> this / x
'*' -> this * x
else -> throw IllegalArgumentException("Not supported")
}
Using an extension function maybe (?) makes the syntax a little nicer. You decide.
Call site:
println(5.doOperation('+', 6))
You can use the interpreter class in beanshell library
to convert string text automatically to result
for example
interpreter.eval("equal=2*3")
println(interpreter.get("equal").toString().toDouble().toString())
or can use expression class that does the same thing
fun String.stringToConditionalOperators(): (Boolean, Boolean) -> Boolean {
return when (this.lowercase(Locale.getDefault())) {
"and" -> {
{ a: Boolean, b: Boolean ->
a && b
}
}
"or" -> {
{ a: Boolean, b: Boolean ->
a || b
}
}
// You can add more operator 🤩
else -> {
return { a: Boolean, b: Boolean ->
a || b
}
}
}
}
Usage..
val operator = "AND"
operator.stringToConditionalOperators().invoke(one, two)

Use argument passed to when in branch condition in Kotlin?

I have some code that roughly looks like this:
val myObject = myObjectRepository.findById(myObjectId);
when {
matchesSomething(myObject) -> doSomethingWithMyObject(myObject)
matchesSomethingElse(myObject) -> doSomethingElseWithMyObject(myObject)
else -> log.warn("No match, aborting");
}
While this works I would think that the following (which doesn't work) would be an improvement if I only need access to myObject inside the scope of when:
when(myObjectRepository.findById(myObjectId)) { myObject ->
matchesSomething(myObject) -> doSomethingWithMyObject(myObject)
matchesSomethingElse(myObject) -> doSomethingElseWithMyObject(myObject)
else -> log.warn("No match, aborting");
}
The error I get here is:
Unresolved reference: myObject
Can you do something like this in Kotlin and if so how? If not, is there a particular reason for why this shouldn't be allowed?
As shown in the documentation, the proper syntax would be
val myObject = myObjectRepository.findById(myObjectId);
when {
matchesSomething(myObject) -> doSomethingWithMyObject(myObject)
matchesSomethingElse(myObject) -> doSomethingElseWithMyObject(myObject)
else -> log.warn("myObject not found, aborting")
}
Or, to actually match what your first snippet does:
val myObject = myObjectRepository.findById(myObjectId);
when(myObject) {
null -> log.warn("myObject not found, aborting");
matchesSomething(myObject) -> doSomethingWithMyObject(myObject)
matchesSomethingElse(myObject) -> doSomethingElseWithMyObject(myObject)
}
You have to be careful about the syntax. In a while we use an arrow -> which has nothing to do with lambdas. I think this is what you were trying in your example.
The only valid syntax for when is this:
when (x) {
1 -> print("x == 1")
2 -> print("x == 2")
else -> { // Note the block
print("x is neither 1 nor 2")
}
On the left side of the arrow -> you declare what the object (x) is being matched against, whereas on the right side you tell what will be executed in that case. Read about this here.
In your example you tried to chain multiple -> which does not work.
This is supported as of Kotlin 1.3. It's referred to as "Capturing when subject in a variable" and looks like this (taken from their documentation):
fun Request.getBody() =
when (val response = executeRequest()) {
is Success -> response.body
is HttpError -> throw HttpException(response.status)
}

Kotlin null safety?

Let's have a function foo and a class Bar:
fun foo(key: String): String? {
// returns string or null
}
class Bar(x: String, y: String) {
// ...
}
Now, let's have the code:
val x = foo("x")
val y = foo("y")
if (x.isNotEmpty() && y.isNotEmpty())
return Bar(x, y)
The problem is that this code will not compile. Since it needs the Bar(x!!, y!!).
However when I replace the function with its content, !! are not needed.
val x = foo("x")
val y = foo("y")
if ((x != null && x.length() > 0) && (y != null && y.length() > 0))
return Bar(x, y)
Why it is not possible to resolve the null check from the function .isNotEmpty()?
This is possible in theory, but it would mean that either
1. The declaration of isNotEmpty() must convey to the compiler the fact that x is guaranteed to be non-null if the result is true
2. A change to a body of any function may cause its call sites to fail to compile.
Option 2 is definitely unacceptable. Option 1 requires a rather expressive mechanism in the type system, which we decided not to add at the moment, because it is likely to complicate things for the user.
We are planning to support something like this with inline functions, but it's still under consideration.