Magnet Patternを使って、type erasureで同じ型になるオーバーロードの問題を解決する
久しぶりにプログラミングネタ。
背景
List[Int]もList[String]もバイトコードでは同じ
type erasureについて知ってる人は飛ばしてOK。
知らない人向けに簡単に説明すると、Scalaでは(Javaでも同じだけど)以下の様なメソッドのオーバーロードは定義出来ない。理由はList[String]とList[Int]がバイトコードレベルでは同じなので、2つのfooメソッドが同一のシグネチャになるため。詳しくは「type erasure」や「型消去」でググって欲しい。
// won't compile class C { def foo(list: List[Int]): SomeResult = { // ... } def foo(list: List[String]): SomeResult = { // ... } }
Magnet Pattern
元ネタ
ググったところ、このblogに詳しく書いてあった。なので、詳しくはそっちを読んでくれ、と言うと話は終わってしまうし、英語が苦手な方もいるだろうし少し説明しよう。
と思ってもう一度ググってみたらゆろよろさんが1週間前くらいに同じネタで記事書いてたので、詳しくはそっちを見て欲しい。
コード例
ゆろよろさんのブログによるとScala 2.10でないと使えないような感じだけど、戻り値が一定でよければScala 2.9でも普通に使える。
scala> :paste // Entering paste mode (ctrl-D to finish) trait FooMagnet { def apply(): List[String] } object FooMagnet { implicit def fromStringList(list: List[String]) = new FooMagnet { def apply() = list } implicit def fromIntList(list: List[Int]) = new FooMagnet { def apply() = list.map("Int: " + _.toString) } } def printFoo(magnet: FooMagnet) = { magnet().foreach(println) } // Exiting paste mode, now interpreting. defined trait FooMagnet defined module FooMagnet printFoo: (magnet: FooMagnet)Unit scala> printFoo(List(1, 2, 3)) Int: 1 Int: 2 Int: 3 scala> printFoo(List("a", "b", "c")) a b c scala> printFoo(List(1.0, 2.0, 3.0)) <console>:10: error: type mismatch; found : List[Double] required: FooMagnet printFoo(List(1.0, 2.0, 3.0)) ^
繰り返しになるけど、printFooというメソッドをオーバーロードを使って定義することが出来ない。
パターン名について
上の例は、元のMagnet Patternで紹介されているのより簡略化されているので、これをMagnet Patternと呼ぶかは不明。
そもそも、これが「Pattern」と呼べるかというのはどっかのMLでも軽く話題になってた気がするし、昨日の勉強会でも話に出てた。
ま、個人的にはそう言う用語の細かい定義にはあまり興味が無いけど。
まとめ
type erasureのせいでオーバーロードメソッドが定義できなくてむぐぐって事態にはいくつか回避方法がある。自分が最初書いたManifestを使った方法でもできるけど、個人的にはこのブログで書いた方法の方がスッキリしてる気がする。