無限リスト

某勉強会で『ふつうのHaskellプログラミング』を読んでいる。
4年前に読んだはずなのだが内容はすっかり忘れていた。身についていなかったということだろう。今回もそうなりそうだが、Scalaを使いこなす基礎知識が少しは得られそう。
今回の勉強会で、与えられたテキストに行番号を付加するサンプルを見ている時、サンプル中の行に行番号を付加する関数

zipLineNumber :: [String] -> [(Int, String)]
zipLineNumber xs = zip [1..] xs

で、「無限リスト"[1..]"が使えるのはいいよね」「Rubyにも無限リスト欲しいよね」(なぜかこの勉強会はRuby使い率が高い)という話になった。

ということで、サンプル本体のnumbering関数をScalaで実装してみる。

def seq(i:Int):Stream[Int] = Stream.cons(i, seq(i + 1))
def numbering(s:String) = s.split("\n")
                           .zip(seq(1))
                           .map(e => "%06d %s".format(e._2, e._1))
                           .mkString("\n")

1行目は無限リストを作る関数。Rubyでも1.9からのEnumeratorを使えば同様のことができるらしい。
でも、ここは無限リストを使う必要は無いよね?

def numbering(s:String) = {
                            val lines = s.split("\n")
                            lines.zip(1 to lines.size)
                                 .map(e => "%6d %s".format(e._2, e._1))
                                 .mkString("\n")
                          }

zipWithIndexという便利メソッド発見。

def numbering(s:String) = s.split("\n")
                           .zipWithIndex
                           .map(e => "%6d %s".format(e._2 + 1, e._1))
                           .mkString("\n")

Rubyだと1.9からEnumeratorが便利になったので

def numbering(s)
  s.split("\n").map.with_index{|e, i| "%6d %s"%[i + 1, e]}.join("\n")
end

と、zipも使わないでよさそう。1.9使ってないので動作未確認(そろそろ入れるか)。

戻って、この例ならHaskellでも無限リスト[1..]を使わず[1..length xs]でもいいはずなのだが、xsがこの時点で評価されてしまうのが嬉しくないのか。