What is the proper way to code a read-while loop in Scala?
Asked Answered
I

3

26

What is the "proper" of writing the standard read-while loop in Scala? By proper I mean written in a Scala-like way as opposed to a Java-like way.

Here is the code I have in Java:

MessageDigest md = MessageDigest.getInstance( "MD5" );
InputStream input = new FileInputStream( "file" );
byte[] buffer = new byte[1024];
int readLen;
while( ( readLen = input.read( buffer ) ) != -1 )
    md.update( buffer, 0, readLen );
return md.digest();

Here is the code I have in Scala:

val md = MessageDigest.getInstance( hashInfo.algorithm )
val input = new FileInputStream( "file" )
val buffer = new Array[ Byte ]( 1024 )
var readLen = 0
while( readLen != -1 )
{
    readLen = input.read( buffer )
    if( readLen != -1 )
        md.update( buffer, 0, readLen )
}
md.digest

The Scala code is correct and works, but feels very un-Scala-ish. For one it is a literal translation of the Java code, taking advantage of none of the advantages of Scala. Further it is actually longer than the Java code! I really feel like I'm missing something, but I can't figure out what.

I'm fairly new to Scala, and so I'm asking the question to avoid falling into the pitfall of writing Java-style code in Scala. I'm more interested in the Scala way to solve this kind of problem than in any specific helper method that might be provided by the Scala API to hash a file.

(I apologize in advance for my ad hoc Scala adjectives throughout this question.)

Impropriate answered 10/6, 2010 at 1:40 Comment(3)
My answer to stackoverflow.com/questions/2849303 may be helpful.Saltarello
@Rex I'd use Iterator instead of Stream. After all, it is one-shot and not re-usable. Besides, Iterator has better memory performance in such tasks.Griffon
@Daniel - Agreed. I believe that there was some good reason at one point why I used Stream before, but I can no longer recall what (and I don't think it remains true anyway). Regardless, here Iterator.continually should be fine.Saltarello
P
30

Based on Rex's post that he mentioned:

Stream.continually(input.read(buffer)).takeWhile(_ != -1).foreach(md.update(buffer, 0, _))

You should replace the var readLen + while {...} lines with it, it produces the same result.

As Rex mentioned, it works with scala 2.8.

Puttier answered 10/6, 2010 at 7:41 Comment(3)
If Stream gives you the willies, you can also just use Iterator.continually.Sizar
When I try it on an InputStream I got from a Process, the foreach method is just called for the first character and then stop. When used with while, I get all the data. Any idea of why?Askew
how do I get the amount of bytes read from "input.read(...)" to use it in "foreach"?Purgation
M
9

What Rex Kerr suggests in his comment is the following:

val md = MessageDigest.getInstance("MD5")
val input = new FileInputStream("foo.txt")
val buffer = new Array[ Byte ]( 1024 )
Stream.continually(input.read(buffer))
  .takeWhile(_ != -1)
  .foreach(md.update(buffer, 0, _))
md.digest

The key is the Stream.continually. It gets an expression which is evaluated continually, creating an infinite Stream of the evaluated expression. The takeWhile is the translation from the while-condition. The foreach is the body of the while-loop.

Marinemarinelli answered 10/6, 2010 at 8:10 Comment(0)
H
0

What about a curried function? You 11 lines of Scala code become:

val md = MessageDigest.getInstance(hashInfo.algorithm)
val input = new FileInputStream("file")
iterateStream(input){ (data, length) => 
    md.update(data, 0, length)
}
md.digest

The iterateStream function on line 3, which you could add to a library is:

def iterateStream(input: InputStream)(f: (Array[Byte], Int) => Unit){
    val buffer = new Array[Byte](512)
    var curr = input.read(buffer)
    while(curr != -1){
        f(buffer, curr)
        curr = input.read(buffer)
    }
}

The ugly duplicated code (where the input is read) ends up in the library, well tested and hidden away from the programmer. I feel that the first block of code is less complex than the Iterator.continually solution.

Houseclean answered 3/2, 2013 at 21:16 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.