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わかりにくい
慣れ?などなど、いろいろありましたとさ。
他にもあったかもしれないけど、思い出したら追記していきます。