Assignment not allow in while expression?

Hi,

In Java, I can do thing like this:

``

BufferedReader reader = new BufferedReader(reader);
String line = null;
while ((line = reader.readLine()) != null) {
  System.out.println(line);
}

However in Kotlin such thing is not allowed. I get error like “Assignments are not expressions, and only expressions are allowed in this context”

So the best I can come up with is something like this, but I wonder is there a better to write this in Kotlin? I like the Java syntax better because it’s shorter and easier for me to read.

``

var line : String?
do {
     line = reader.readLine()
     if (line == null)
          break
     println(line)
} while (true)

Thanks,
Zemian

7 Likes

So personally I'm quite glad that assignment is not an expression in Kotlin ... your version is shorter, but personally I don't find it easier to read. To my mind the second version more clearly expresses the (slightly unusual) control flow.

That said, hopefully eventually the Kotlin standard library will add an extension property to BufferedReader so that you can do something like this

``

for (line in reader.lines) {
  println(line)
}


If anyone is interested, here is the necessary property extension

``

import java.io.BufferedReader
import java.util.NoSuchElementException
val BufferedReader.lines: Iterator<String>
  get() = object : Iterator<String> {
  var line = this@lines.readLine()
  override fun next(): String {
          if (line == null)
           throw NoSuchElementException()
          val result = line!!           // no idea why !! is needed here, compiler bug?
          line = this@lines.readLine()
          return result
  }
  override fun hasNext() = line != null
  }

1 Like

All you need is available in the standard library:

http://jetbrains.github.com/kotlin/versions/snapshot/apidocs/kotlin/io/java/io/File-extensions.html#forEachLine(jet.String, jet.Function1)

1 Like

I think you guys miss the question I tried to ask. I undertand there is kotlin.io package that helps out the Java Stream/Reader classes. And I do see how higher function can help designing the API. However this is not my question in orginal post.

My question is more about the usage of the Kotlin while statment itself, I just happen to give you example using Reader class. Often cases I used the while loop in a case like the first post I gave, and I can capture result from a method all in the single line and then compare it inside the while expression. I understand there is may way to do the same, so I already provided one style, which Tom said he prefer it rather but I dislike. Out of all the Java verboseness, I find this particular case Java is good at. I am just stating this particular case and pointed Kotlin doesn’t support it and I miss it. You can take this as a feedback on your restriction of the while statement in kotlin I guess.

To be honest, the only use case for this idiom I (or my colleagues) ever came across is the one you mentioned in the post. Expressions with side effects are generally discouraged, so I think we do well enough in this case.

2 Likes

So File.forEachLine works for files .... but it doesn't work for all the BufferedReader cases ... for example,

val reader = BufferedReader(StringReader(someStringData))

or

val reader = BufferedReader(InputStreamReader(myInputStream))

So the extension for BufferedReader is still pretty useful :slight_smile:

You could do it like this:

  val reader = BufferedReader(reader)
  var line: String? = null;
  while ({ line = reader.readLine(); line }() != null) {
  System.out.println(line);
  }

15 Likes

Ah ha! Exellent workaround Jerome! Thank you very much for the tips.

I forgot that “{}” is a literal construct for jet.Function0 and we can enclose any var. This certainly avoided assignment inside direct while statment, but still able to store and capture the line var result. However this solution is bit heavy though, since we are creating a function object per each iteration. Interesting …

I am curious as why Kotlin wont’ simply allow assignment operator to be evaluated. That way, we can omit that “; line” part. :slight_smile:

Andrey,

To be honest, the only use case for this idiom I (or my colleagues) ever came across is the one you mentioned in the post.

That's not a good reason to not take feedback from users. :) If you search the "BufferedReader.readLine()" within JDK itself, you will see few occurance of the same usage. Also I see prentty out in the open source community.

1 Like

My point was the the readLine() and read() use cases are the only ones out there. Since they are covered with the library, there's no need in this pattern at all.

A scenario where assignment is useful as an expression is one like:

class IntRange(public final val start: Int, public final val end: Int) : Iterable<Int> {   override fun iterator(): Iterator<Int> = IntIterator(this) }

class IntIterator(final val range: IntRange) : Iterator<Int> {
  private var current: Int = range.start
  override fun next(): Int = (current = current + 1) // this does not compile because an assignment isn’t an expression that returns the value assigned like in many other languages
  override fun hasNext(): Int = current <= range.end
}


This is a contrived case as an example, but the point is that having an assignment evaluate to the value of the assignment allows the programmer to assign a value and return it without having to assign and return as separate steps.

1 Like

Why not use "++current"?

That only works if you are incrementing by 1.

++current

is the same as

{ current = current + 1; return current; }

In fact, ++i is a perfect example of an assignment as an expression and it is immensly useful.  ++i both assigns to i and as an expression it returns the value that was assigned to i.  Increment and decrement operators are just examples of situations where assignments as expressions are very useful, I would just like to see them extended to more than the trivial increment-by-one and decrement-by-one operations.

For the slightly more complex case consider:

i += 2


To take it a little further:

current = FetchNext(current)

where FetchNext is some operation that gets the next item in a series from a remote source.

Currently, in Kotlin these would have to be,

{ i += 2; return i}
{ current = FetchNext(current); return current }

respectively.

3 Likes

Ok, I see your point.

Since these cases are not very common, the overhead is rather moderate and many code style guides explicitly prohibit using assignments as expressions, I think what we have now is a fair compromise.

2 Likes

How can I simplify the following similar code without performance reduce:

randomAccessFile.seek(offset)

var byteCount = randomAccessFile.read(buffer)
while (byteCount != -1){
    fos.write(buffer, 0, byteCount)
    byteCount = randomAccessFile.read(buffer)
}
1 Like

The code you’ve posted is the correct way to implement this logic in Kotlin. Given that this pattern only comes up in a very small number of library functions, we don’t think that the language has to provide a way to simplify it.

Ok, got it, thanks for your awesome job!:grin:

I don’t know is it much slower but there is also an option with ‘tailrec’:

tailrec fun copy(buffer:ByteArray, fos:OutputStream, randomAccessFile:RandomAccessFile) {
    val byteCount = randomAccessFile.read(buffer)
    if (byteCount != -1){
        fos.write(buffer, 0, byteCount)
        copy(buffer, fos, randomAccessFile)
    }
}

I personally prefer it this way

randomAccessFile.seek(offset)

while (true) {
    val byteCount = randomAccessFile.read(buffer)
    if (byteCount < 0) break
    fos.write(buffer, 0, byteCount)
}

IMHO, this is the most clear way to write this kind of code. No invisible side-effects, no code duplication, looping is explicit. No var at all, just a val.

As for lines, Kotlin’s null-safety features enable a much better idiom for all these kind of readLine iterations:

val reader = BufferedReader(reader)
while (true) {
    val line = reader.readLine() ?: break
    System.out.println(line);
}

I don’t miss Java-style pattern of using readLine at all. This is clearly much better. No vars and no nulls here.

17 Likes

@Jacksgong Kotlin allows you to create your own while (with blackjack and hookers?), so you can create an inlined function like this one:

inline fun <T> `while`(nextValue: () -> T, condition: (T) -> Boolean, body: (T) -> Unit) {
    var value = nextValue()
    while (condition(value)) {
        body(value)
        value = nextValue()
    }
}

And then you can use it like this everywhere you want:

`while`({ randomAccessFile.read(buffer) }, { it != -1 }) { byteCount ->
    fos.write(buffer, 0, byteCount)
}
4 Likes