Coming on Stream その5

| # Comments
Coming on Streamシリーズ ファーストシーズン最終回。
その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クラスとimplicitensureCloseメソッドにより、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わかりにくい

慣れ?


などなど、いろいろありましたとさ。
他にもあったかもしれないけど、思い出したら追記していきます。

comments powered by Disqus

Twitter Icon

AdSense

Creative Commons License
このブログはクリエイティブ・コモンズでライセンスされています。
Powered by Movable Type 5.14-ja

Google検索

カスタム検索

2013年10月

    1 2 3 4 5
6 7 8 9 10 11 12
13 14 15 16 17 18 19
20 21 22 23 24 25 26
27 28 29 30 31