4

In Java sometimes i write the code as follows:

 String obj = null;
 while ((obj = getObject()) != null) {
    // do smth with obj
 }

In Kotlin compile-time error is shown:

Assignments are not expressions, and only expressions are allowed in this context

What's the best equivalent in Kotlin?

4ntoine
  • 19,816
  • 21
  • 96
  • 220
  • (I know you said not to suggest "better" ways, but that's the way :) ) – Federico klez Culloca Jan 31 '19 at 08:13
  • @FedericoklezCulloca I think OP meant the readLine as an example, and not a way to read lines – Lino Jan 31 '19 at 08:14
  • @Lino Right, that was just an example of "assign and check". Updated the question. – 4ntoine Jan 31 '19 at 08:14
  • 1
    From: https://discuss.kotlinlang.org/t/assignment-not-allow-in-while-expression/339/4, you can use: `while ({ line = readLine(); line }() != null)` – Lino Jan 31 '19 at 08:15
  • 2
    @4ntoine got it. Voted to reopen. – Federico klez Culloca Jan 31 '19 at 08:17
  • @Lino, it's working but i guess it's bad for performance reason as it's just lambda call that returns object – 4ntoine Jan 31 '19 at 08:17
  • @4ntoine on the other hand, if something is not idiomatic to the language, it's bad form (and, as you said, a possible performance problem) to try to force it. It would be like trying to force GOTO-style into java. As a general rule, just go with what the language offers. – Federico klez Culloca Jan 31 '19 at 08:19
  • [`generateSequence { getObject() }.forEach { obj -> ... }`](https://stackoverflow.com/a/54456912/6202869) may help you here... added it as answer to the duplicated question... – Roland Jan 31 '19 at 09:15
  • I think it is still a duplicate... just that `readLine` got too much attention there... but the question is the same... what is an appropriate equivalent to `var x : Any?; while ((x = someFunction()) != null) {` in Kotlin? – Roland Jan 31 '19 at 09:19
  • IMHO each such situation is better served by a custom helper function like `Reader.eachLine {}`. The general is more involved thou. Voted to reopen. – voddan Jan 31 '19 at 09:19
  • which answer you want to give to this question which is not already in [Assignment not allowed in while expression?](https://stackoverflow.com/questions/41537638/assignment-not-allowed-in-while-expression) – Roland Jan 31 '19 at 09:26

5 Answers5

5

I would rather give up fanciness and do it the old-school way, which is instead most intuitive.

 var obj = getObject();
 while (obj != null) {
    // do smth with obj
    obj = getObject();
 }
Ricky Mo
  • 6,285
  • 1
  • 14
  • 30
  • the only pity is that you are writing the same code basically twice then... and the more you do in the `while`-loop the more it gets hidden... :-/ – Roland Jan 31 '19 at 11:15
3

The simplest ad-hock solution is probably

while(true) {
    val obj = getObj() ?: break
}

However special cases are IMO best served by specialized helper functions. For example reading a file line by line can be done with a helper readLines as explained in an answer to a similar question:

reader.forEachLine {
    println(it)
}
voddan
  • 31,956
  • 8
  • 77
  • 87
  • 2
    I don't really like the while(true) with a break. I don't think it's a good practice because it's counterintuitive, and hard to debug. It could be useful sometimes for uncommon situations, but it's unusual... – Ctorres Jan 31 '19 at 09:44
  • replace `getObj()` with `reader.readLine()` and you have the same answer as in the previously duplicated question,... (https://stackoverflow.com/a/43557893/6202869) (but: no down-vote from me) – Roland Jan 31 '19 at 09:55
3

In cases you just want to replace while ((x = y.someFunction()) != null) you may use the following instead:

generateSequence { y.someFunction() }
          .forEach { x -> /* what you did in your while */ }

generateSequence will extract you all the values one by one until the first null is reached. You may replace the .forEach with a reduce or fold (or anything else that seems appropriate ;-)) if you want to keep the last value or sum up the values to something else.

If you need to check against something else, you may just add something like takeIf, e.g.:

generateSequence { y.someFunction().takeIf { /* yourCondition... */ } }

basically just repeating what I also mentioned here.

Roland
  • 22,259
  • 4
  • 57
  • 84
  • 1
    why? I added something about `useLines`, etc. there, which is not really relevant here.... but I also think this is a duplicate question of the linked one... – Roland Jan 31 '19 at 09:48
  • Ah yes, I just saw it earlier and looked *almost* the same, that's why I mentioned it – Lino Jan 31 '19 at 09:58
2

I've tinkered around and have come up with a neat general helper function:

inline fun <T> (() -> T?).untilNull(action: (T) -> Unit) { 
    while (true) action(this() ?: break) 
}

Which can be called like this:

::getObject.untilNull { /* do something with "it" */ }

You can of course don't use this helper function and just stay with the while

while(true){
    val result = getObject() ?: break
    // do something with "result"
}

Also another solution would be to create an inline lambda and then imediatly invoke that:

var result = null
while ({ result = getObject(); result }() != null){
    // do something with "result"
}

This could probably be optimized if you'd "save" the lambda first:

var result = null
var assignment = { result = getObject(); result };
while (assignment() != null){
    // do something with "result"
}
Lino
  • 19,604
  • 6
  • 47
  • 65
  • hmm... except of the `untilNull` I have seen all the variations of this answer also [here](https://stackoverflow.com/questions/41537638/assignment-not-allowed-in-while-expression)... and to be honest... that `untilNull`-usage looks a bit strange to me... – Roland Jan 31 '19 at 09:57
  • @Roland Yes it is odd, because it combines a lot of different things, inline, generics, extension functions, lambdas and of course the elvis operator with a `break`. It *could* be written more verbose, but I think that is primarly code style one would like to follow – Lino Jan 31 '19 at 10:00
  • still I see no benefit (not in readabilty, nor something else) to use `::getObject().untilNull` when there exists `generateSequence { getObject() }` which basically does the same (well... you get a sequence there which I will favor in nearly all cases ;-)) – Roland Jan 31 '19 at 10:03
  • @Roland I look at `Sequence`s the same as I look at java 8 `Stream`s, they express perfectly what you want to do (of course only if you use them correctly), but they generate tons of overhead, lots and lots of objects created which mostly just get discarded. The same is true for all those `Collections.forEach()` usages, why not use a normal `for-loop`? Again, I think this is primarly opinion based, what may be the best approach. – Lino Jan 31 '19 at 10:06
  • 1
    speaking of impact: what is the impact of `::getObject().untilNull`... `untilNull` is `inline`, so the `block` is probably ok... but what about that `::getObject()` itself? ;-) the overhead you mentioned regarding `Stream`/`Sequence` is (depending on the use-case of course) often negligible... The benefits you get: readability, short-circuiting, etc. often overweigh the drawbacks... – Roland Jan 31 '19 at 10:11
  • 1
    @Lino I think a better alternative to your last would be `while (run { result = getObject(); result != null }) ...`. _Without_ saving the lambda. Then it'll get inlined (and IMO more readable). – Alexey Romanov Jan 31 '19 at 13:31
  • 1
    @Roland `::getObject` is also a function argument to an `inline fun`, it'll get inlined too. – Alexey Romanov Jan 31 '19 at 13:32
  • @AlexeyRomanov what exactly do you mean with your last comment? Will it also inline smoothly if it's actually a function extension function? – Roland Jan 31 '19 at 14:30
  • @Roland The receiver of an extension function is just another argument, so it can be inlined (if it has function type and it's a lambda or a method reference, not e.g. a variable). – Alexey Romanov Jan 31 '19 at 16:55
  • @AlexeyRomanov isn't the benefit of having an inline function that it is inlined into the code where it is called? What you write is certainly true for non-inlined extension functions, but is it true also for inlined ~function extension function? I assume (but don't know) the inlining of the function extension function will not inline the receiver itself, but it might do so for the parameters.. and if that's the case, what would then be the benefit (or disadvantage) of using such a construct? If I am wrong, then it might even be worth its own question.. – Roland Jan 31 '19 at 20:30
0

How about:

while (getObject().apply { obj = this } != null) {
    // ...
}
Willi Mentzel
  • 27,862
  • 20
  • 113
  • 121