33

I use opencsv to parse csv files, and my code is

while( (line = reader.readNext()) != null ) { .... }

I got a compiler warning saying:

 comparing values of types Unit and Null using `!=' will always yield true
 [warn]     while( (aLine = reader.readNext()) != null ) {

How should I do the while loop?

Lydon Ch
  • 8,637
  • 20
  • 79
  • 132

5 Answers5

71

An assignment expression has type Unit in Scala. That is the reason for your compiler warning.

There is a nice idiom in Scala that avoids the while loop:

val iterator = Iterator.continually(reader.readNext()).takeWhile(_ != null)

This gives you an iterator over whatever reader.readNext returns.

The continually method returns an "infinite" iterator and takeWhile takes the prefix of that, up to but not including the first null.

(Scala 2.8)

mkneissl
  • 4,902
  • 2
  • 26
  • 28
  • Hmm. My answer does not really match the question. I'll keep it, it might be useful anyway. – mkneissl Jun 17 '10 at 15:30
  • Why don't you think your answer matches the question? – Ken Bloom Jun 25 '10 at 03:34
  • @Ken: portoalet asked for an explanation for the warning about Unit. I answered with a loop idiom. I've just added the explanation for the warning to make the answer complete. – mkneissl Jun 25 '10 at 11:04
  • thanks for clarifying. I was trying to figure out what it was about your code that *didn't* do the same thing as his while loop. Since it does the same thing, the reason your answer didn't match the question is because it didn't talk about the warning. – Ken Bloom Jun 25 '10 at 13:56
  • and how I execute code "inside" `iterator`? Like I'd execute it "inside" while loop. – Incerteza Aug 09 '14 at 05:54
  • 2
    Is it worth pointing out that this `Iterator` will not evaluate eagerly, unlike the `while` block will _execute_ eagerly? – Dan Gravell Feb 24 '16 at 16:01
20

In your case (line = reader.readNext()) is a functional literal that returns type Unit. You may rewrite the code as follows:

while( {line = reader.readNext();  line!= null} ) { .... }
Vasil Remeniuk
  • 20,519
  • 6
  • 71
  • 81
  • 8
    Please do not accept this answer: whilst true it is not idiomatic Scala and there are much better ways of achieving what you want (see my answer below) – oxbow_lakes Jun 17 '10 at 17:51
  • 3
    @oxbow_lakes, perhaps you are being a bit stern here? The while keyword is used hundreds of times in the 2.8 standard library by the core team and this answer is performant, concise and most importantly, looks like my answer too ;@)) – Don Mackenzie Jun 17 '10 at 20:19
  • 10
    I stand by the comment, I'm afraid. This is not idiomatic. It's not even more performance than the *functional* answers because your code will get compiled to a function (i.e. object) instantiation – oxbow_lakes Jun 21 '10 at 09:34
18

You can use a Stream to get what you want:

Stream.continually(reader.readLine()).takeWhile( _ ne null) foreach { line =>
  //do Stuff
}

This has the added advantage of other cool stuff as well:

Stream.continually(reader.readLine()).takeWhile( _ ne null) match {
  case head #:: tail => //perhaps you need to do stuff with the first element?
  case _             => //empty
}

EDIT - thanks to mkneissl for pointing out I should have included this warning:

scala> Stream.continually(1).take(100000000).foreach(x=>()) 

scala> val s = Stream.continually(1).take(100000000) 
s: scala.collection.immutable.Stream[Int] = Stream(1, ?) 

scala> s.foreach(x=>()) java.lang.OutOfMemoryError: Java heap space
oxbow_lakes
  • 133,303
  • 56
  • 317
  • 449
  • 3
    You might want to add a warning not to keep the head of the Stream to avoid running out of memory. Have been caught by this recently when reading huge files. scala> Stream.continually(1).take(100000000).foreach(x=>()) is ok, but scala> val s = Stream.continually(1).take(100000000) s: scala.collection.immutable.Stream[Int] = Stream(1, ?) scala> s.foreach(x=>()) java.lang.OutOfMemoryError: Java heap space [ bah, no formatting in comments, see pastie: http://paste.pocoo.org/show/226682/ ] – mkneissl Jun 17 '10 at 21:37
  • 2
    Unless you actually need a Stream, you might as well use Iterator.continually instead of Stream.continually, then you have no worries about problems with Stream. – Seth Tisue Jun 22 '10 at 18:08
  • 1
    Right Seth, see my answer to this question: http://stackoverflow.com/questions/3062804/scala-unit-type/3063092#3063092 and for a comparison of Stream vs. Iterator see http://stackoverflow.com/questions/1527962/difference-between-iterator-and-stream-in-scala . – mkneissl Jun 22 '10 at 22:15
  • @mkneissl not sure I understand why the OOM error would happen, could you elaborate on the OOM issue? – Mehdi May 09 '19 at 15:40
  • 1
    @MehdiB. , see https://www.scala-lang.org/api/current/scala/collection/immutable/Stream.html : """One must be cautious of memoization; you can very quickly eat up large amounts of memory if you're not careful. The reason for this is that the memoization of the Stream creates a structure much like scala.collection.immutable.List. So long as something is holding on to the head, the head holds on to the tail, and so it continues recursively.""" – mkneissl May 12 '19 at 11:16
10

You are writing Scala code they way you would write it in Java. Try doing it in a more Scala-like way. To read a text file line by line and do something with each line, try this:

import java.io.File
import scala.io.Source

Source.fromFile(new File("myfile.txt")).getLines.foreach { line =>
    // Do something with the line, for example print it
    println(line)
}
Jesper
  • 202,709
  • 46
  • 318
  • 350
  • He's not doing plain file IO. He's doing CSV IO. – Ken Bloom Jun 25 '10 at 03:31
  • 1
    @Ken CSV files are normally line-oriented text files, and his code snippet above is about reading a text file line by line. My answer indeed isn't a direct answer to his question, I just wanted to show a way that fits more with the Scala idiom; his original code is Java idiom in Scala. – Jesper Jun 25 '10 at 06:44
3

Assignment in Scala doesn't return a value, Unit is similar to void in C or C++.

try

var line = ""

while ({line = reader.readNext(); line != null}) { ... }

This works because the value of the last expression in a block is returned and in this case it is a Boolean which is required by the while

Don Mackenzie
  • 7,953
  • 7
  • 31
  • 32