Coming on Streamシリーズ ファーストシーズン最終回。
その1
その2
その3
その4
最終回として、今回もろもろ利用したScala的な何かについてつらつらと書きます。
今回利用するプログラムも、
を利用します。
また https://github.com/ueshin/hbase-twitter/tree/hbase-twitter-0.0.2でブラウズできます。
その1
その2
その3
その4
最終回として、今回もろもろ利用したScala的な何かについてつらつらと書きます。
今回利用するプログラムも、
$ git clone https://github.com/ueshin/hbase-twitter.git
$ cd hbase-twitter
$ git checkout hbase-twitter-0.0.2を利用します。
また https://github.com/ueshin/hbase-twitter/tree/hbase-twitter-0.0.2でブラウズできます。
便利に利用したScalaの機能
まずは便利に利用したScalaの機能もろもろについて。package object
プロジェクト内でよく利用する共通処理をプロジェクトのトップパッケージにパッケージオブジェクトで置いておくと便利です。package.scala
サブパッケージのクラスでこれらを参照する場合には、
package st.happy_camper.hbase.twitter
package subpackageのようなパッケージ宣言にしておけば利用可能になります。
パッケージ内のみで利用するものも、それぞれのパッケージオブジェクトで書いておくと便利です。
implicit conversion
HBaseではほとんどのデータをバイト配列で保持します。この時、Javaだとバイト配列への変換もすべて書かなければなりません。
そのため
Bytes.toBytes() というメソッド呼び出しを繰り返し記述する必要があります。そこで、必要な変換を
implicit conversion すると便利です。package.scala 9-14行目
例えば、 TweetHotTag.scala の35行目
new HTable() の第2引数や、 37行目 new Scan().addColumn() の各引数などは全てバイト配列なのですが、implicit conversionのおかげですっきりと記述出来ています。Before
new Scan().addColumn(Bytes.toBytes("score_" + lang), Bytes.toBytes(TagScoring.dateFormat.format(target)))After
new Scan().addColumn("score_" + lang, TagScoring.dateFormat.format(target))クロージャによる新構文
Scalaでは、クロージャを利用することで新構文を作成できます。正確には
implicit conversionとかただのメソッド呼び出しがそれっぽく見えるように記述できるってことなんですが。今回作成したのは、使い終わったら
close()を呼び出す必要があるものを、自動的にclose()してくれる構文です。package.scala 16-26行目
この
EnsureCloseクラスとimplicitなensureCloseメソッドにより、close() メソッドを持つオブジェクトを、処理を記述したクロージャを引数にしてopen()すると、処理した後にclose()してくれます。例えば、TweetHotTag.scala の35行目
new HTable(conf, "tagtrend").open {
case tagtrend: HTable => ...
}や、38行目の
tagtrend.getScanner(new Scan() ... ).open {
case scanner: ResultScanner => ...
}のように書けます。
ここを
try { ... } finally { close() } で記述するとものすごく冗長に見えます。と、これを書いている間に上記のクロージャの引数となる部分の、
caseと型を記述しなくてもいいようになる方法を思いつきましたが、それはまたの機会に。unapplyメソッド
メソッド名を省略できるapplyメソッドもかなり便利ですが、Scalaの強力なパターンマッチで暗黙的に利用されるメソッドであるunapplyメソッドを利用することで便利なことが多いです。Status.scala 40-47行目
NodeオブジェクトからOption[Status]オブジェクトを返すメソッドです。パターンマッチで使えば、引数に指定した変数に
Statusオブジェクトが代入されます(HTableHandler.scala 19行目)。XML.loadString(xml) match {
case Status(status) => ...
case Delete(delete) => ...
case _ => ...
}ScoreWritable.scala 34-43行目
こちらはバイト配列から
Option[Score]オブジェクトを返すメソッドですが、このunapplyメソッドは変数の代入時にも使えるので、例えば(TweetHotTag.scala 42行目)val ScoreWritable(score) = result.valueのように、バイト配列から
score変数にScoreオブジェクトをさくっと代入できてしまいます。ただし、
unapplyメソッドがNoneを返したら例外飛ぶので注意。面倒だったところ
Eclipseのプラグインがちゃんと動かないのは置いといて。型引数が厳密
Javaよりも型引数を厳密に記述しなければならない場合があります。例えば(TagScoring.scala 50行目)、
classOf[HRegionPartitioner[ImmutableBytesWritable, Put]]Javaだと
HRegionPartitioner.class だけです。また、前のバージョンですが(LangCounter.scala 25行目)、
type Context = Mapper[ImmutableBytesWritable, Result, Text, LongWritable]#Contextのように、ジェネリクスな親クラスの内部クラスの型を指定するときに、親クラスの型引数込みで内部クラスを指定しなければならない場合があります。
※ これってなんでですかね? ご存じの方、教えてください。
※ もっと簡潔に書けるよっていう方法があれば、そちらでも。w
Java併用時
デフォルトでimportされるクラス名をScala側のクラスが上書きしてしまうとコンパイルエラーが分かりにくくなります。例えば(CountReducer.scala 12行目)、
override def reduce(key: Text, values: java.lang.Iterable[LongWritable], context: Context)の第2引数の型を
Iterable[LongWritable] だけで指定するとコンパイルエラーになります。あと
Integer.parseInt("1") はいけるけど Long.parseLong("1") はいけないとか。java.langパッケージはデフォルトではimportしなくてもよかったんじゃなかろうか(そしたらJava側のクラスを使うときには常に気を使うようになる気がする)。もしくは名前をカブらせるのやめて欲しかった(
Stringみたいにできなかったのかなとか)。Scaladocわかりにくい
慣れ?などなど、いろいろありましたとさ。
他にもあったかもしれないけど、思い出したら追記していきます。

