Wicket1.5で導入されるNew Event SystemをScalaでエロガントに実装する
もう2週間以上前にもなりますが、Wicket勉強会 2011-01 : ATNDに行ってきたんですよ。
そこでWicket1.5からNew Event Systemってのが導入されてこれはScalaと相性いいんじゃね?みたいな 話になって、そりゃ俺に対して振ってるんだな、と思って書いてみましたが、そんなことより その後のさゆりさんがどうなったのか気になります。届け!この想い!!
Wicket勉強会開催直前です。 - 矢野勉のはてな日記
はてなブックマーク - Wicket勉強会開催直前です。 - 矢野勉のはてな日記
New Event Systemって
くわしくは以下の資料を見てくださいね。
http://public.iwork.com/document/ja/?a=p38265472&d=Wicket勉強会201101.key
簡単に言うと、コンポーネントとかがイベントを発行したり、イベントを受け取って処理ができたりなようはおなじみのアレです。
イベントとして、任意のオブジェクトをメッセージとして投げることができる。わお!
だが、 いかんせん実装がダサイ。イベントが発行されると、受け取り側のonEventメソッドが呼ばれるので実装しなきゃならないんですが、 こんなコード書かなきゃいけない。
@Override public void onEvent(IEvent<?> event) { super.onEvent(event); if (event.getPayload() instanceof ImageRefresh) { if (selected) { source = null; selected = false; ImageRefresh<?> imageRefreshPayload = (ImageRefresh<?>) event.getPayload(); imageRefreshPayload.addComponent(this); } } }
引数にはIEvent<?>型で、そのIEvent#.getPayload()を呼ぶことで、イベント発行元が投げたメッセージオブジェクトを取得できます。
しかし、なんのオブジェクトが入ってるかわからんので、onEventの引数型はIEvent<?>で中身の型情報は失われており、従ってgetPayloadは Object型を返す、と。
そして、具体的なイベントの種別を判定するために"event.getPayload() instanceof ImageRefresh"とかやらなきゃいけない。
ダサすぎて牛乳吹くわ。"instanceof"が許されるのは(ry
Scalaでやってみる
この手の型に応じた処理の振り分けをScalaで行う場合、ふたつの選択肢があります。ひとつはパターンマッチングで型パターンを指定する方法、 もう一つはgeneralized type constraintsによる型パラメータによるメソッド定義です。
後者の方法は、様々な型のイベントを受け取れるようにするためには向いてないので、ストレートに前者の方法で実装しました。
では、デモで使用されたサンプルを改修してみます。元のソースコードはこちらです。
で、こちらがイベント処理周りをScalaで書き換えたものです。
yuroyoro/wicket15sample · GitHub
Eventを受け取ることができるtrait ReactableComponent
onEventを実装し、イベントを振り分けるtraitとしてReactableComponentを用意します。
trait ReactableComponent extends Component { // このtraitはComponentにmixinできる this: Component => // イベントを処理するPartialFunction[Any, Unit] private val handlers = scala.collection.mutable.ArrayBuffer.empty[PartialFunction[Any, Unit]] // 引数のPartialFunction[Any,Unit]をイベントハンドラとして登録する final def handler( f:PartialFunction[Any,Unit]):Unit = { handlers += f } override final def onEvent(event:IEvent[_]):Unit = { val e = event.getPayload // 登録されてるPartialFunctionで処理できるものにイベント処理を行わせる handlers.filter{ _ isDefinedAt e}.foreach{ f => f(e) } } }
このtraitは、self type annotationでComponentにmixiinされる前提です。
handler関数で引数にもらったPartialFunctionをイベントハンドラとして覚えておき、 onEventが呼び出されるとhandler関数で登録されたPartialFunctionのうち、イベントが処理できるものに処理を行わせるようになっています。
具体的なイベント処理を行うtrait EventReactor
次に、実際にそれぞれのイベント処理を行うtraitとして、EventReactorを用意しました。
メッセージに対応した処理を行う各handlerは、このtraitを継承します。
trait EventReactor extends Component { this: ReactableComponent => }
EventReactorは、self type annotationでさっきのReactableComponentがあらかじめmixinされてる前提です。
ReactableComponentで定義されているhandler関数をするためです。
では、実際のhandlerを見てみます。以下は、RemoveRequestというメッセージを受け取ったら自信を非表示にする処理を 行うRemoveReactorと、ImageRefreshイベントを受け取ったら画像のsrcのURLをリフレッシュするImageRefreshReactorです。
trait RemoveReactor extends EventReactor { this:Component with WebMarkupContainer with ReactableComponent => handler{ case event:RemoveRequest => setVisibilityAllowed( !event.isRemove ) } } trait ImageRefreshReactor extends EventReactor { this: Component with ReactableComponent with ImageSrcComponent with SelectableComponent => handler{ case event:ImageRefresh[_] => if (selected) { reload() selected = false event.addComponent(this) } } }
具体的なイベント処理は、handler関数にPartialFunctionで渡している箇所で行っています。handler関数は PartialFunctionを引数にとり、かつ{case x:Type => ...}のように書くとPartialFunctionに変換される、という仕組みを利用しています。
実際に、これらのtraitをmixinしたコンポーネントを見てましょう。
class RssImage(id:String) extends AjaxLink[java.lang.Void](id) with ReactableComponent with ImageSrcComponent with SelectableComponent with ImageRefreshReactor with RemoveReactor { import scala.util.control.Exception._ def getImageSource() = allCatch opt{ val app = WicketApplication.get val rss = app.getRssSource val entry = rss.getRandomEntry entry.getEnclosures.get(0).asInstanceOf[SyndEnclosure].getUrl } override def onClick(target:AjaxRequestTarget ) = { selected = !selected; target.add(this); } }
AjaxLinkを継承したRssImageは、ImageRefeshReactorとRemoveReactorをmixinしているため、RemoveRequestを受け取ると非表示になり、ImageRefreshを受け取ると画像を再描画します。しかし、このComponent自身にはどこにもイベント処理が記述されていません。
EventReactorトレイトがイベントの型を型パラメータまたは抽象型メンバーでもらわない設計になっているのは、ちゃんと理由があります。
trait EventReactor[A] {...} trait RemoveReactor extends[RemoveRequest] {...} trait ImageRefeshReactor extends[ImageRefresh] {...}
上記のような設計になっていないのは、RemoveReactorとImageRefreshReactorが同じComponentに同時にmixinされる可能性があるためです。同じ継承先のEventReactorの型変数をそれぞれ違う型でextendsしたtraitを同時にmixinすると、traitの線形化ができなくなってコンパイルエラーとなります。
traitによって個々の処理をPartialFunctionで登録するパターンマッチング
traitで処理を定義してる、handlers{ case x:Type => ...}って箇所、何かに似ていると思いませんか?
これは、scalaのActorライブラリでのメッセージ処理に似ています。ってか、このアイデアはActorからぱくりました。
加えて、self type annotationでmixinできる対象を限定し、self type annotationで指定した型に定義されている関数などを使ってtrait側で抽象化した処理を実装できるようになっているのがポイントです。
WicketのComponentのように複雑なクラス階層に対して横断的にtraitで処理を織り込む方法は、AOPに似てますがはるかに簡潔で安全です。
ね?Scalaって、エロガントでしょう?