3

Background info

A common pattern in some programming languages is to have a function that when called, returns the next value until the end of the finite sequence is reached, in which case it keeps returning null.

A common example in Java is this:

void printAll(BufferedReader reader) {
    String line;

    // Assigns readLine value to line, and then check if not null
    while ((line = reader.readLine()) != null) {
        System.out.println(line);
    }
}

It is similar to the iterator in the Iterator design pattern, but the iterator has a next(): Object and a hasNext(): Boolean, whereas the BufferedReader has no hasNext() check functionality, but only the form next(): Object?, in which the returned object can be null to mark the end of the sequence. I call functions such as next() a "next function" (or maybe "yield" function), but I don't know if there is an word for this pattern.

In Java, an expression can contain assignments, which allows constructs such as: (line = reader.readLine()) != null. This code assigns the nullable value of readLine() to line, and then check whether the value in line is not null. But Kotlin doesn't allow such constructs, because in Kotlin, an assignment is not an expression, so it cannot be used as loop condition in Kotlin.

Question

What are the possible patterns in Kotlin to loop through the finite number of values returned by a next function, such as readLine()? (Next functions can also be found for example in ZipInputStream, to go to the next zip entry.)

I'm not simply looking for a Kotlin workaround for this problem, because I can program that myself without problems. I'm looking to explore the possible patterns so that people can select one that suits their needs.

I have found some patterns myself, which I'll post here as an answer, but there may be more patterns out there, which would be interesting to know.

1 Answer 1

6

I've ordered to solutions by (what I believe) the best solution in descending order.

Solution 1: using built-in generateSequence (recommended)

I just found out that Kotlin has a built-in standalone generateSequence() function (located in the kotlin.sequences package).

generateSequence { br.readLine() }
    .forEach { line ->
        println("Line: $line")
    }

generateSequence accepts a code block that you can provide, that must generates a value. In this case, br.readLine() is the code block, and generates either a String, or null if the end is reached. generateSequence generates a sequence that internally calls readLine() when the next value is requested from the sequence, until readLine() return null, which terminates the sequence. So sequences in Kotlin are lazy: they don't read neither know all the values ahead of time, only a single readLine() is called when for example forEach processes a single line. This laziness is usually exactly what you want, because it saves memory and minimizes an initial delay. To change it to eagerly, you can append generateSequence { br.readLine() } with .toList().

  • Pros 1: no additional variables.
  • Pros 2: just one construct (generateSequence).
  • Pros 3: returns a Sequence, so you can chain additional methods such as filter().
  • Pros 4: any sign of nullability is abstracted away. (No null keywords, nor ? nor ! operators.)
  • Pros 5: adheres a functional programming style.

IMO, this is the cleanest solution that I've seen so far.

Solution 2: while true loop with elvis break call

while (true) {
    val line = br.readLine() ?: break
    println("Line: $line")
}
  • Pros: no additional variables.
  • Cons: some people don't like while-true loops and break statements.

Solution 3: do-while with safe call also

do {
    val line = br.readLine()?.also { line ->
        println("Line: $line")
    }
} while (line != null)
  • Pros: no additional variables.
  • Cons: less readable than other solutions.

Solution 4: next before start and at end of each iteration

This is probably the most common solution for Java programmers who are new to Kotlin.

var line = br.readLine()
while (line != null) {
    println("Line: $line")
    line = br.readLine()
}
  • Cons 1: duplicated next (readLine) call and a duplicated assignment.
  • Cons 2: reassignable var.

Solution 5: while loop with assignment using also

This is the solution generated by IntelliJ when converting Java to Kotlin code:

var line: String?
while (br.readLine().also { line = it } != null) {
    println("Line: $line")
}

Cons: line is declared as nullable, even though it can never be null inside the loop. So you'll often have to use the not-null assertion operator if you want to access members of line, which you can limit to one assertion using:

var nullableLine: String?
while (br.readLine().also { nullableLine = it } != null) {
    val line = nullableLine!!
    println("Line: $line")
}
  • Cons 1: requires not-null assertion even though it can never be null inside the loop.
  • Cons 2: reassignable var.
  • Cons 3: less readable than other solutions.

Note that if you change var line: String? to var line: String, the code still compiles, but it will throw a NPE when line becomes null, even though there are no not-null assertions used.

Sign up to request clarification or add additional context in comments.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.