kmizuの日記

プログラミングや形式言語に関係のあることを書いたり書かなかったり。

Iterator.continually()を使おう

Scalaの標準IOライブラリであるscala.io(.Source)は非常に腐ってます。読み込みしか対応してない上に、バイト列の読み込みもサポートしてないという代物。しかも、Scala 2.7まではscala.io.Sourceがそのままだとcloseできなかったりとか(今はcloseできます)。さっさと退場して欲しい代替ライブラリが標準になって欲しいところなんですが、現状の情勢をみるにしばらく先になりそうな感じです。

というわけで、Scalaを使うならJavaのIOライブラリとお付き合いしなければいけません。とりあえず、Commons IOとか使うのも良いかもしれませんが、ちょっとしたものを書くときにいちいちCommons IO使うのも面倒くさいです。

じゃあ、BufferedReaderとかをいちいちwhileループで回すのかといえばそれも面倒くさいです。そこで、その苦痛を緩和してくるのが、Iterator.continually()です。使い方は簡単。なんかループしたい処理をIterator.continually()の引数に渡すだけです。試しに標準入力から一行ずつ読み取って、行番号付きで出力する処理を書いてみましょう。

val lines = Iterator.continually(readLine()).takeWhile(_ != null).toList
for ((line, lineNum) <- lines.zipWithIndex) {
  printf("%d:%s%n", lineNum + 1, line)
}

たったこれだけです(もうちょっと短くできますが、それはおいときます)。Iterator.continually()は、引数で与えられた処理を無限回繰り返すIteratorを返してくれるので、呼ぶたびに返り値が変わる入力系のメソッド/関数と相性が良いです。もちろん、無限に読み取られても困るので、どっかでぶった切ってあげる必要があります。それがtakeWhile(_ != null)の部分で、これを入れることによって、readLine()がnullを返した時点で終了するIteratorが返ってきます。後は、それをtoListやらtoSeqでimmutableなシーケンスに変換してあげるだけ。簡単ですね。もちろん、readLine()以外にもこれは応用できます。たとえば、標準入力から1バイトずつ読み込んで配列に変換して、文字列として出力する処理は次のように書くことができます。

val bs = Iterator.continually(System.in.read()).takeWhile(_ != -1).map(_.toByte).toArray
println(new String(bs, "US-ASCII"))


なお、ほとんど同じようなことをしてくれるメソッドにStream.continually()がありますが、メソッドや関数内で完結する処理に対して、あえてこちらを使う必要はないと思います。Iterator.continually() 対 Stream.continually() の議論については、こちらを参照してください。