k4200’s notes and thoughts

Programmer side of k4200

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 = {
    // ...
  }
}
解決方法

一応、解決方法はいくつか思いついた。

  • fooの中で要素の型をチェックして処理を分岐
  • Manifestの型によって分岐

で、とりあえず後者で実装してたんだけど(※1)、もっといい方法はない?ってScala勉強会で質問した所、りりろじるんるんさんから以下の2つを教えてもらった。

  • implicitパラメータを使う方法(※2)
  • Magnet Patternを使う方法

※1 Manifestを使った方法は、勉強会のスライドの後半部分を参照。
※2 異なるimplicit parameterを渡すことによってシグネチャを変える、らしい。詳細は省略。

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を使った方法でもできるけど、個人的にはこのブログで書いた方法の方がスッキリしてる気がする。