wataメモ

日々のメモをつらつらと書くだけ

Scalaを使ってみた

 最近はPerlを使っていることもあり、スクリプト言語の良さを理解出来るようになってきた。動的な型で得られるメリットとして生産性の向上というか記述が簡潔になることが上げられると思う。duck typingの柔軟性を知ってしまうと、静的な型の言語は面倒に感じてしまう。自分で書くなら動的の方が生産性が高いのではと本気で思う。そんな中でも大規模開発する上ではJavaは非常に良い言語だと思う。静的な型の強みと、言語仕様のシンプルさが開発者の敷居を下げ、出来上がるコードのブレを軽減することが出来ると考えているからだ。そんなジレンマを抱えながら、ふと思い出したのが「Scala」、「Groovy」と「Xtend」の存在だ。Groovyは前に少し触ったことがあったが、現状では何がトレンドなのかと調べてみるとScalaっぽい。気になっている「Play framework」でもScala使えるみたいだしということでScalaを触って見ることにした。

 まずは開発環境ということで「ScalaIDE」を入れた。なぜかEclipse4.2系に入らなかったので、3.7.2に入れた。

 最初は軽い気持ちで触ってみようと考えていたが、言語仕様がPDFで191ページ。そして歴史もあるっぽい。(歴史があると何かを調べた時に引っかかった情報が過去のバージョンの情報で今風の書き方で無かったり、最悪動かなかったりもする為、学習コストがかさむ)というわけで今後も時間をかけて触っていかないと覚えられないと感じた。その為とりあえずHello World!的な感じでやってみた。さすがにHello World!ではすぐ終わってしまうので、簡単な課題をすることにしてみた。

CVSにコミットした並び順は順不同でモジュール名の重複がある「モジュール名[半角スペース][リビジョン]」のフォーマットでリスト化されているファイルがある。そこからモジュール名としてユニークな一覧を作り、そのリビジョンはファイルの中で一番リビジョンが新しいのをリビジョンとして読み込んだファイルと同じフォーマットで表示する。

CVSのリビジョンなので今回は1.Xを基本とし、2.Xは無い物として扱う。(マイナーバージョンのみで比較する。)ファイルの中身のイメージは以下。

src/wata/Hoge.java 1.5
src/Moge.java 1.1
src/wata/Fuga.java 1.10
src/wata/Hoge.java 1.4

 やってみた結果は以下。

package wata
import java.lang.{RuntimeException => RE}
import scala.collection.mutable.HashMap
import scala.io.Source

object Release {
  def main(args: Array[String]) = release()
  def release(fn: String = "module.txt") {
    val fmt = """([^ ]+) ([1-9]+)\.([0-9]+)"""
    val ml = fmt.r
    val m = new HashMap[String, Int]
    val src = Source.fromFile(fn)
    src.getLines.foreach( (v) => {
      if (!v.matches(fmt)) throw new RE(v)
      val ml(f, u, l) = v
      val r = l.toInt
      if (r > m.getOrElse(f, -1)) m += (f -> r)
    })
    src.close
    m.keys.toList.sorted.foreach( (v) => printf("%s 1.%d\n", v, m(v)))
  }
}

 これがScalaっぽいのかは分からないがとりあえず解説。

 まずは「package」や「import」。

package wata
import java.lang.{RuntimeException => RE}
import scala.collection.mutable.HashMap
import scala.io.Source

 基本Javaと同じだがセミコロンが省略出来るので基本書いていない。RuntimeExceptionは長いので別名が定義出来るので省略形(RE)にした「{RuntimeException => RE}」。

 次は「object」宣言。

object Release {

 Scalaでも通常クラスは「class」で宣言するがstaticが存在しないScalaではstaticメソッド(今回ではmainメソッド)を作りたい場合は「object」して宣言する。mainメソッドは特別だとは思うがobject宣言してもstaticになるわけではなくシングルトンになる。

 次は「def」宣言。

def main(args: Array[String]) = release()

 これはメソッドの宣言で通常は「def メソッド名(引数名: 型, …): 戻り型 { /* 処理 */ }」の様に書く。戻り値がない場合はUnit(Javaで言うvoid)とも書けるが、Unitの場合は省略出来るので省略して書いている。中括弧「{}」もif文等と同じで1行なら省略可能なので書いていない。

 メソッドの引数はデフォルト値を指定出来る。

def release(fn: String = "module.txt") {

 「String = "module.txt"」が引数が渡されなかった場合の変数「fn」の値となる。

 次は文字列定数定義について。

    val fmt = """([^ ]+) ([1-9]+)\.([0-9]+)"""

 ダブルクォートを3つ「"""」にすることで、中身に円マーク「\」と改行がエスケープせずに書けるようになる。そして型は無いので「val」で受けているが「var」でも受けることは可能。違いとしては「val」は一度代入すると2度と代入出来ない。(Javaのfinal)

 次は関数「r」について。

    val ml = fmt.r

 正規表現Regexクラスのインスタンスを返す。

 次は「HashMap」。

    val m = new HashMap[String, Int]

 ほぼJavaと同じで型も指定する。

 次はファイルの解析部分。

    valsrc = Source.fromFile(fn)
    src.getLines.foreach( (v) => {
      if (!v.matches(fmt)) throw new RE(v)
      val ml(f, u, l) = v
      val r = l.toInt
      if (r > m.getOrElse(f, -1)) m += (f -> r)
    })
    src.close

 「Source」でファイルを読み込み「getLines」で1行ずつ読み込み、それを「foreach」で回している。Scalaの構文の特徴としてメソッド呼び出しの書き方として「オブジェクト.メソッド(引数)」を「オブジェクト メソッド 引数」の様に、空白で区切って呼び出すことも出来る。そして引数がない場合はカッコ「()」も省略出来る。なので前の「fmt.r」も「fmt r」と書くことも出来る。このシンタックスシュガーは1から10までループする時に以下のように書きたいから作ったのだと思う。

for (i <- 1 to 10) print(i + "\n") 

  この「1 to 10」は「1.to(10)」と同義でScalaでは全てがオブジェクトなので1も恐らくRichIntクラスのtoメソッドを呼んでいることになる。これ以外にも同じような感じで使えるので、なかなか面白い考えだ。

    src.getLines.foreach( (v) => {
      if (!v.matches(fmt)) throw new RE(v)
      val ml(f, u, l) = v
      val r = l.toInt
      if (r > m.getOrElse(f, -1)) m += (f -> r)
    })

 foreachにはメソッドを渡していて、引数はvとして1つ受け取ると定義している。まずはフォーマットチェックしてマッチしなければ例外を投げる。その次は正規表現にマッチしたら各グループを順番に変数「f, u, l」に代入する。つまり「f」には「([^ ]+)」でマッチした値が入り、「u」には「([1-9]+)」にマッチした値が入るという具合だ。これは文字列として来るので大小を比較する為に文字列を「toInt」メソッドで数値化する。その後まとめ用のHashMapにモジュールをキーで入れたリビジョンと比較して大きければ上書きしている。なぜかScalaのHashMapは存在しないキーを「get」すると例外が発生してしまう。させたくない場合は「getOrElse」でない場合に返す値を指定しておく。そして「put」する場合は「+=」で出来る。これは演算子のオーバーライドに見えるが、Scala的には「+=」というメソッドの様だ。

 そして最後の表示部分。

    m.keys.toList.sorted.foreach((v) => printf("%s 1.%d\n", v, m(v)))

 HashMapに作ったユニークなモジュールのキー値だけを「keys」メソッドで取り出し、「toList」メソッドでリスト化して「sorted」メソッドでソートして「foreach」メソッドでループする。先ほどと同じ1つ1つの値は「v」変数に入ってくる。HashMapから「get」するには「m(v)」とも書ける。これは実際は「apply」メソッドを呼んでいることになるので「m.apply(v)」と等価だ。Scalaでは「apply」メソッドを呼び出す時はメソッド名を省略可能としているので「m(v)」と書けるわけだ。「m[v]」と書けた方が個人的にはしっくり来るのだが。

 というわけで簡単に触ってみた。感触としては非常に面白い。もう少し長く付き合って行きたい言語ではある。ただ、ある意味とても癖があるので変態言語と認識されて万人受けはしないだろう。書き方も色々あったり、性能問題が発生する可能性もあるので業務では使える場所は選びそう。興味がある人は是非趣味として嗜んでみては如何だろうか。