2012年2月27日月曜日

mapBetween関数Scala版 その3

前回で関数の実装はまともになったので、クラスを拡張する方向に検討をしてみる。


とりあえずmapBetween関数をtraitにして分離。(20〜24行目)
前回までは、型パラメタAを下限とする型パラメタTを関数の引数の型としていたが、traitにしたことでまたうまく関数の型推論ができなくなってかえって使いにくいため、Aそのものを引数の型に変更した。匿名関数が手軽に定義できる以上、JavaのComparatorのような問題は起きないと思って良いのかもしれない。

使う際にはSeqのサブクラスをnewする時にwith ExtSeq[A]でtraitをミックスインする(3行目)ことで、mapBetween関数を呼び出すことができるようになる。(4〜6行目)
ミックスインできるのはインスタンス化するときなので、生成されたインスタンスを返すRichIntクラスのtoメソッドは使えない。Rangeクラスのコンストラクタはfrom, to, stepの3つの引数を必ず要求するため、ちょっと使いにくい。

そこで、Seqのサブクラスのインスタンスを trait ExtSeqをミックスインしたクラスのインスタンスに変換する、暗黙の型変換を行う関数を定義する。(14〜17行目)

この暗黙の型変換は、mapBetweenオブジェクトのスコープ内で有効なため、Rangeクラスのインスタンスを普通に生成(8行目)しても、そのインスタンスに対してmapBetween関数を呼び出そうとすることで、コンパイル時に暗黙的にseqToExtSeq関数の呼び出しが加えられる。それによって、あたかもRangeクラスに元々mapBetween関数が定義されていたかのように見える。(9〜12行目)

このseqToExtSeq関数を定義する際に注意することは、返り値をExtSeqにしなければならないのを誤ってSeqのインスタンスを返すように実装(例えば++=の代わりに++を呼んでしまった等)しても、コンパイルが通ってしまうことだ。そうした場合、実行時にseqToExtSeq関数が呼び出されると、Seqのインスタンスを本来の返り値の型であるExtSeqに暗黙的に変換しようとして、自分を再帰的に呼び出してしまい、無限ループに陥る。しかもタチの悪いことに、末尾最適化されてしまってスタックがオーバフローしないため、ただダンマリになってしまってしばらく何が起きたのかわからなかった。実装時はimplicit修飾子を付けないで実装して、後から修飾子を付けたほうが間違いが起こりにくいかも。

ところで、immutableなパッケージでミックスインできるクラスが見つけられなかったためやむを得ずArrayBufferを使ってしまったが、このままでは暗黙の型変換をした後のインスタンスがmutableになってしまう。ココは改善する必要があるのだが、とりあえずノーアイディア。

0 件のコメント:

コメントを投稿