scalaの多相いろいろ
scalaでのいろいろな多相の実装方法とそのpimp my library pattern版をいろいろ書いて比較してみたいと思います。
オーバーロード
これも一応多相と言うらしい。
// オーバーロードによる多相 class John class Taro def greet(john: John) { println("My name is John!") } def greet(taro: Taro) { println("太郎です") } val john = new John() greet(john) val taro = new Taro() greet(taro)
継承
一般的にはこれをポリモーフィズムと呼ぶことが多いと思います。
// 継承による多相 trait Greeter { def greet() } class John extends Greeter { def greet() { println("My name is John!") } } class Taro extends Greeter { def greet() { println("太郎です") } } val john = new John() john.greet() val taro = new Taro() taro.greet()
pimp my library pattern
継承関係になくてもメソッドのように扱うことができる。
// pimp my library class John class Taro trait Greeter { def greet() } implicit class JohnGreeter(john: John) extends Greeter { def greet() { println("My name is John!") } } implicit class TaroGreeter(taro: Taro) extends Greeter { def greet() { println("太郎です") } } val john = new John() john.greet() val taro = new Taro() taro.greet()
型クラス
オーバーロードに近いが、関数自体の定義は1カ所のみ。
他のサンプルと挙動を合わせるためにdef greet[A: Greeter](a: A)
のa
は利用していないが、say(a: A)
として定義すればa
によって挙動を変えることもできる。
// 型クラスによる多相 class John class Taro trait Greeter[A] { def say() } implicit val johnGreeter = new Greeter[John] { def say() { println("My name is John!") } } implicit val taroGreeter = new Greeter[Taro] { def say() { println("太郎です") } } def greet[A: Greeter](a: A) { implicitly[Greeter[A]].say() } val john = new John() greet(john) val taro = new Taro() greet(taro)
型クラスのpimp my library patternバージョン。implicit classとcontext boundを使うと結構きれいに書ける。
// 型クラスによる多相(pimp my library pattern) class John class Taro trait Greeter[A] { def say() } implicit class GreeterOps[A: Greeter](a: A) { def greet() { implicitly[Greeter[A]].say() } } implicit val johnGreeter = new Greeter[John] { def say() { println("My name is John") } } implicit val taroGreeter = new Greeter[Taro] { def say() { println("太郎です") } } val john = new John() john.greet() val taro = new Taro() taro.greet()
型クラス(dependent method types)
型クラスをそのまま書くと戻り値の型は任意の型にできないが(間違ってたらすいません)、dependent method typesを利用することで戻り値の型を任意にすることができる。ただし、context boundで書くことができなくなる。
// 型クラスによる多相(dependent method types) class John class Taro trait Greeter[A] { type Result def say(): Result } implicit val johnGreeter = new Greeter[John] { type Result = Unit def say(): Result = println("My name is John!") } implicit val taroGreeter = new Greeter[Taro] { type Result = Unit def say(): Result = println("太郎です") } def greet[A](a: A)(implicit ev: Greeter[A]): ev.Result = ev.say() val john = new John() greet(john) val taro = new Taro() greet(taro)
magnet pattern
以下の例ではメリットを感じにくいが、これもdependent method typesによって引数の型によって処理と戻り値の型をばらばらにできる。
また、type erasureによる衝突を回避できる。元記事はこちら。
ほぼほぼ型クラスのdependent method types版と同じで、implicit conversionを使うかimplicit parameterを使うか、という感じ。
// magnet patternによる多相 class John class Taro trait GreetingMagnet { type Result def apply(): Result } object GreetingMagnet { implicit class JohnGreetingMagnet(john: John) extends GreetingMagnet { type Result = Unit def apply(): Result = println("My name is John!") } implicit class TaroGreetingMagnet(taro: Taro) extends GreetingMagnet { type Result = Unit def apply(): Result = println("私は太郎です") } } def greet(magnet: GreetingMagnet): magnet.Result = magnet() val john = new John() greet(john) val taro = new Taro() greet(taro)
magnet patternとpimp my library patternの組み合わせ。だいぶごつごつしてしまった。
// magnet patternによる多相 class John class Taro trait GreetingMagnet { type Result def apply(): Result } object GreetingMagnet { def apply(john: John): GreetingMagnet = new GreetingMagnet { type Result = Unit def apply(): Result = println("My name is John!") } def apply(taro: Taro): GreetingMagnet = new GreetingMagnet { type Result = Unit def apply(): Result = println("私は太郎です") } } class GreetingMagnetOps(val magnet: GreetingMagnet) { def greet(): magnet.Result = magnet() } implicit def john2GreetingMagnetOps(john: John): GreetingMagnetOps = new GreetingMagnetOps(GreetingMagnet(john)) implicit def taro2GreetingMagnetOps(taro: Taro): GreetingMagnetOps = new GreetingMagnetOps(GreetingMagnet(taro)) val john = new John() john.greet() val taro = new Taro() taro.greet()
比較
パターン | 多相 | 継承関係 | 戻り値の型変更 | 定義箇所 |
---|---|---|---|---|
オーバーロード | アドホック | 不要 | 可 | N |
継承 | 部分型 | 必要 | 不可 | 1 |
pimp my library pattern | アドホック | 不要 | 一部可 | N |
型クラス | アドホック | 不要 | 一部可 | 1 |
型クラス(dependent method types) | アドホック | 不要 | 可 | 1 |
magnet pattern | アドホック | 不要 | 可 | 1 |
まとめ
従来のオブジェクト指向のデザインパターンとは違ったことができるので、何でもできる一方で設計力が試されますね。
ざっくりとした知識で書いているので、間違っていたらご指摘いただければ!