implicit class を利用して Seq (とか)にメソッドを生やす
case class
の Seq
に対するメソッドを生やしたい時どうするか。例えば、下記のような case class
があるとする。
case class Range( min: Int, max: Int )
これの Seq
つまり、Seq[Range]
に対して、含まれるRangeの中から、最小の min
と 最大の max
を取得し、それを Range
として欲しい時どうするか。利用箇所が1箇所なら、普通に map
とかで頑張って処理しちゃえばいい気もするが、複数箇所で利用したり、そもそもコードがごちゃつくので少し避けたい。かといって、下記のように、それ用のメソッドを用意するのもやりすぎ感ある。
object Range { def minMaxRange(values: Seq[Range]): Range = { val min = values.map(_.min).min val max = values.map(_.max).max Range(min, max) } }
val ranges = Seq( Range(1, 10), Range(2, 9), Range(0, 3), Range(8, 11) ) Range.minMaxRange(ranges)</pre>
呼び出し元も、少し長くなって読みづらい。
結論
case class
のコンパニオンオブジェクトに implicit class
を作成して使う。
case class Range( min: Int, max: Int ) object Range { implicit class RangesOps(values: Seq[Range]) { def minMaxRange: Range = { require(values.nonEmpty) val min = values.map(_.min).min val max = values.map(_.max).max Range(min, max) } } }
こうすることで、Seqに対して定義済みの値があるように使え、呼び出しもとが簡潔になる。
val ranges = Seq( Range(1, 10), Range(2, 9), Range(0, 3), Range(8, 11) ) ranges.minMaxRange // val res0: Range = Range(0,11)
PreScalaMatsuri で似たような発表があったけど、その時はWrapperクラスを用意する方針の話だったかな。。?個人的には無駄で意味のないクラスはできる限り増やしたくないので、現状は implicit class
の利用がいいかなと思ってる。
追記
ラッパークラスを用意する方針は、「コレクションオブジェクト」あるいは「ファーストクラスコレクション」というテクニックに該当することを後で知りました。
ちょっとしたコレクション処理の追加ならばコンパニオンオブジェクトへの implicit class
の追記。コレクションに対して制約がある場合などはコレクションオブジェクトを作るのがいいかなというのが最近の私の方針です。