[Scala] (Stream.constとforをつかった)あついイディオム

via @tanigon

このイディオム? 熱いわ for(i <- Stream.const(()=>in.read(buf)).map(_()).takeWhile(_ != -1))... 

を受けて、ちょっと実験してみた。

ようは、InputStreamなどから、ある条件になるまで読み込んで何かする時のイディオム。
ただ、最初に全部読んだりはしないで、必要に応じて読んでゆく。

テスト用のソースは

import java.io.{FileInputStream, BufferedInputStream, InputStreamReader, BufferedReader}

import scala.io.Source

def test1(filename:String) {
  val in = new BufferedInputStream(
            new FileInputStream(filename))

  val buf = new Array[Byte](BUF_SIZE)

  val stream = Stream.const(() => in.read(buf))
  for(i <- stream.map(_()).takeWhile{_ != -1}) {
    println(i)
    println("[" + buf.subArray(0,i).map(b => String.format("%X", b.asInstanceOf[Object])).mkString + "]")
  }
}

def test2(filename:String) {
  val in = new BufferedReader(
            new InputStreamReader(
              new FileInputStream(filename),encode))
  val buf = new Array[Char](BUF_SIZE)

  val stream = Stream.const(() => in.read(buf))
  for(i <- stream.map(_()).takeWhile{_ != -1}) {
    println(i)
    println("[" + buf.subArray(0,i).mkString + "]")
  }
}

def test3(filename:String) {
  val in = new BufferedReader(
            new InputStreamReader(
              new FileInputStream(filename),encode))
  val stream = Stream.const(() => in.readLine)
                      .map(_())
                      .takeWhile{_ != null}
  for(line <- stream) {
    println("[" + line + "]")
  }
}
  
val BUF_SIZE = 64
val encode = "shift-jis"
val filename = "input.txt"

println("----------")
println(バイト単位)
println("in:InputStream, buf:Array[Byte]")
test1(filename)
println("----------")
println(文字単位(含む改行コード))
println("in:Reader, buf:Array[Char]")
test2(filename)
println("----------")
println(行単位の読み込み(除く改行コード))
test3(filename)

入力ファイルは

line(行): 1
line(行): 2
line(行): 3
line(行): 4
line(行): 5
line(行): 6
line(行): 7
line(行): 8
line(行): 9
line(行): 10
line(行): 11
line(行): 12
line(行): 13
line(行): 14
line(行): 15
line(行): 16
line(行): 17
line(行): 18
line(行): 19
line(行): 20

で実行結果は・・・

----------
バイト単位
in:InputStream, buf:Array[Byte]
64
[6C696E6581698D73816A3A2031DA6C696E6581698D73816A3A2032DA6C696E6581698D73816A3A2033DA6C696E6581698D73816A3A2034DA6C696E65]
64
[81698D73816A3A2035DA6C696E6581698D73816A3A2036DA6C696E6581698D73816A3A2037DA6C696E6581698D73816A3A2038DA6C696E6581698D73]
64
[816A3A2039DA6C696E6581698D73816A3A203130DA6C696E6581698D73816A3A203131DA6C696E6581698D73816A3A203132DA6C696E6581698D7381]
64
[6A3A203133DA6C696E6581698D73816A3A203134DA6C696E6581698D73816A3A203135DA6C696E6581698D73816A3A203136DA6C696E6581698D7381]
55
[6A3A203137DA6C696E6581698D73816A3A203138DA6C696E6581698D73816A3A203139DA6C696E6581698D73816A3A203230DA]
----------
文字単位(含む改行コード)
in:Reader, buf:Array[Char]
64
[line(行): 1
line(行): 2
line(行): 3
line(行): 4
line(行): 5
line]
64
[(行): 6
line(行): 7
line(行): 8
line(行): 9
line(行): 10
line(行)]
64
[: 11
line(行): 12
line(行): 13
line(行): 14
line(行): 15
line(行]
59
[): 16
line(行): 17
line(行): 18
line(行): 19
line(行): 20
]
----------
行単位の読み込み(除く改行コード)
[line(行): 1]
[line(行): 2]
[line(行): 3]
[line(行): 4]
[line(行): 5]
[line(行): 6]
[line(行): 7]
[line(行): 8]
[line(行): 9]
[line(行): 10]
[line(行): 11]
[line(行): 12]
[line(行): 13]
[line(行): 14]
[line(行): 15]
[line(行): 16]
[line(行): 17]
[line(行): 18]
[line(行): 19]
[line(行): 20]

いい感じですね。

3番目のは、結果だけ見れば以下とほぼ同じ

def test4(filename:String) {
  val source = Source.fromFile(filename, "shift-jis")
                      .getLines
  for(i <- source) {
    val line = i.stripLineEnd
    println("[" + line + "]")
  }
}

ちなみに、

@ScalaTohoku によると、

Scala 2.8 では、もう少しスマートに
for(i<-Stream.continually(in.read(buf)).takeWhile(_ != -1)){...}
と書けるようになりますね。

だそうです。
覚えといて損は無い感じです。

1点だけ心残りなのは、test1,test2の

  val stream = Stream.const(() => in.read(buf))
  for(i <- stream.map(_()).takeWhile{_ != -1}) {
    println(i)
    println("[" + buf.subArray(0,i).map(b => String.format("%X", b.asInstanceOf[Object])).mkString + "]")
  }

の部分をくくりだして共通化したかったのだけれど、よくわからなかった><

[追記]
ちなみに、現状はここで止まっている。

type Readable[A] = { def read(buf:Array[A]):Int }

def test0[A](in:Readable[A], buf:Array[A])(func:(Int) => Any) {
  val stream = Stream.const(() => in.read(buf))
  for(i <- stream.map(_()).takeWhile{_ != -1}) { func(i) }
}

def test1b(filename:String) {
  val in = new BufferedInputStream(
            new FileInputStream(filename))

  val buf = new Array[Byte](BUF_SIZE)

  test0(in,buf){ i =>
    println(i)
    println("[" + buf.subArray(0,i).map(b => String.format("%X", b.asInstanceOf[Object])).mkString + "]")
  }
}

上記のコードはコンパイルは通るけど、

java.lang.NoSuchMethodException: java.io.BufferedInputStream.read(scala.runtime.BoxedArray)

がでる。(惜しい感じ・・・)