![pexels-photo-5423829.jpeg](/_next/image?url=https%3A%2F%2Fimages.ctfassets.net%2Fai4vjpehjr3j%2F15YtitPK59gymQ67AbLBwS%2F1ab3de1ea3914290491cc2e5f11b8c84%2Fpexels-photo-5423829.jpeg&w=3840&q=75)
Scala summoner pattern
![Kazuki Moriyama](/_next/image?url=https%3A%2F%2Fimages.ctfassets.net%2Fai4vjpehjr3j%2F2Oa6QzkYOe51LQiER7VEtN%2F6e819c38a98a1c649401b86861c40d53%2Fimage.png&w=96&q=75)
scalaではimplicitを利用して型クラスのインスタンスを解決するが、summoner patternというものを使えばimplicitを愚直に書くより少し楽になるよというお話。
まずは普通に
combineという二項演算のみできる型クラスSemigroupを考える。
trait Semigroup[T] {
def combine(x: T, y: T): T
}
// Int用インスタンス
implicit def intSemi: Semigroup[Int] = (x: Int, y: Int) => x + y
// なんか普通のメソッドなのにimplicitのせいで長く見えない?
def plus[T](x: T, y: T)(implicit semigroup: Semigroup[T]): T = semigroup.combine(x, y)
plus(1, 2) // => 3
まあよく見るやつですね。
これのimplicit parameterを書くのがめんどくさいなぁ。
Context Bound
上の課題はcontext boundという機能を使えば解決できる。
trait Semigroup[T] {
def combine(x: T, y: T): T
}
implicit def intSemi: Semigroup[Int] = (x: Int, y: Int) => x + y
// TはSemigroup型のインスタンスを持つことが保証される
def plus[T: Semigroup](x: T, y: T): T = implicitly[Semigroup[T]].combine(x, y)
plus(1, 2) // => 3
plusのシグネチャで書いているT: Semigroup
というのがcontext boundでimplicitパラメタのシンタックスシュガーである。
使うための条件はTがその後ろの型の型パラメタとして入る、つまりT: F = implicit f: F[T]
である。
だからEitherみたいなジェネリクスが2つある型コンストラクタには使えない。
あとはSemigroup[T]が変数に束縛されなくなってしまったので、それをimplicitlyで取り出せばいい。
でもこのimplicitlyも書くのめんどくさいなぁ。
Summoner Pattern
summoner patternを使った最終形は以下。
trait Semigroup[T] {
def combine(x: T, y: T): T
}
object Semigroup {
// summoner method
def apply[T: Semigroup]: Semigroup[T] = implicitly[Semigroup[T]]
}
implicit def intSemi: Semigroup[Int] = (x: Int, y: Int) => x + y
def plus[T: Semigroup](x: T, y: T): T = Semigroup[T].combine(x, y)
plus(1, 2) // => 3
簡単に言えば型クラスのコンパニオンオブジェクトにsummoner methodと呼ばれるものを定義すればいいのだ。
御存知の通りscalaではapplyメソッドは特別な扱いを受ける。
すなわち上の例でSemigroup[T] = Semigroup.apply[T]
はimplicitly[Semigroup[T]]
を呼び寄せる。
あとはContext Boundのときと同じ。
上の例だとただimplicitの書く場所変わっただけじゃないかという感じだが、いろんなとこでSemigroupインスタンスが使い回されてくると、効果を発揮する。