Assignment not allow in while expression?

I thought I could use the assignment with the Elvis operator, so it would become some kind of getInstance() method:

return resources ?: resources = Resources()

So return resources, but if it is null assign it and return that.

It seems to be that this code is working:

do {
     val line = reader.readLine()
     println(line)
} while (line != null)
2 Likes

This code prints the last null which is not what you usually want do.

Recently I’ve realized that since break is also an expression in Kotlin, you can take advantage of Elvis operator and write it as follows:

val input = "ABC\nDEF"
val reader = input.reader().buffered()

while (true) {
    val line = reader.readLine() ?: break
    println(line)
}
3 Likes

This is such a common use-case that we can even create the following extension function

interface LoopContext {
    fun breakLoop()
}

class LoopContextImpl : LoopContext {
    var breaked = false
    override fun breakLoop() {
        breaked = true
    }
}

inline fun BufferedReader.forEachLine(process: LoopContext.(String) -> Unit) {
    val loop = LoopContextImpl()
    while(!loop.breaked) {
        val line = readLine() ?: break
        loop.process(line)
    }
}

fun example() {
    "E\nx\na\nm\np\nl\ne".reader().buffered().forEachLine {
        if (it == "m") breakLoop()
    }
}

Note that it support breaking out of the loop using an intermediate object (which should be mostly removed by the git after performing escape analysis).

It of course is a question of fashion, but I think that functional (stream-like) solution is better:

  reader.takeLinesUntil{<condition>}.forEach{<action>}

@darksnake, My problem with functional style approach for such cases is that you can implement it in two ways

  1. eager - which will buffer to an intermediate, possibly very large, list (on the takeLinesUntil)
  2. lazy - which requires constructing a complex set of intermediate objects (the stream and the closures - which cannot be inlined) this approach not only strains your garbage collector but also, with the current tool-set, is a debug hell

I do not agree. The statement about garbage collector need to be proved. There are some minor performance issues with java 8 streams due to generation of additional anonymous classes, but kotlin lambdas are little bit different.
Lambdas could be inlined. In fact, internal implementation of takeLinexUntil could be exactly the same as one you proposed, with only difference that it does not consume the line, but delegates it to external consumer.
As for debugging, I do not see any problem here. IDEA debugger allows perfectly well to debug lambdas.

As I already said it is just functional style vs imperative style.

In the lazy case (streams) not really - we must store the closures of lazy operations (like the takeLinesUntil) so that we will be able to invoke them once a terminal operation (the forEach) is performed - so such lambdas cannot be inlined.
In the eager case on the other hand - they can be inlined but then as I mentioned in the previous post - we must create an intermediate (possibly large) list to store the results for the next step

The problem is not debugging once inside the lambda but between lambdas, and in addition the call-stack is a mess

In my work we performed many benchmarks on the case - I cannot publish them but it is quite easy to perform some yourself. In addition, there are many benchmarks on java 8 streams online:

In addition you can read this blog post: https://dzone.com/articles/java-lambdas-and-low-latency
keep in mind that once you need to store the closures (as in the stream case) - they most probably will not be removed via escape analysis

link is unavialable

The forEachLine() function is documented here.

“Easier to read” matters mostly if you live in a vacuum and have not been exposed to existing programming languages. The “assignment is an expression” idiom is common and well understood by most anyone that’s coded.

Isn’t one of Kotlin’s claim to fame is that it’s less verbose than Java?

1 Like

That’s entirely untrue.

2 Likes

val line = reader.readLine() ?: break

THAT IS ABSOLUTELY BEAUTIFUL! A perfect, concise, readable one-liner! Thanks!

5 Likes

Assignment is not an expression, what if make it return some value to become an expression?
It’s even more concise than Java~

    while (reader.readLine()?.let {println(it)} != null);

Where is the assignment in your example? This is just one big expression.

That’s a clever idea, however let expression returns the last statement, so you should replace it with also.

1 Like

Just do your check in a .also{} that returns false if readLine() is null

var line: String? = null;
while(true.also{ line = readLine() ?: return false})
    println(line!!)

The !! is not required, but your IDE may find a false positive, because it does not understand that the null check is in the .also{}

You can even do

while(true)
    println(readLine() ?: break)

I’d be careful with that statement. It is true in your example, but if the nullable value comes from a property there are many reasons why it might be null even after the null check (multithreading and custom get/setters).

You can mark line as lateinit to avoid the null thing.