2017月07月28日

Aux Patternの紹介

引数の型に応じて戻り型を変化させる方法といえばDependent Method Type / The Magnet Patternがあります。

例)

trait Foo[A]{ 
  type B 
  def foo(a:A):B 
} 

object Foo{ 
  implicit val intFoo: Foo[Int] = new Foo[Int]{ 
    type B = String 
    def foo(i:Int): String = i.toString
  } 

  implicit val stringFoo: Foo[String] = new Foo[String]{ 
    type B = Long 
    def foo(i:String): Long = i.toLong
  } 
} 

def foo[A](a:A)(implicit M:Foo[A]): M.B = M.foo(a)

利用例

scala> foo(1)
res0: Foo.intFoo.B = 1

scala> foo("123")
res1: Foo.stringFoo.B = 123

が、この方法では 型クラスを複数使うと引数同士を参照してくれない ので下記のような実装はコンパイルが通りません。
※Scala 2.x系の仕様で、implicit parameterは複数定義できません。Scala 3のDottyコンパイラでは解決されるそうです。

scala> def bar[A](a: A)(implicit M:Foo[A], N:Monoid[M.B]): M.B = N.zero
error: illegal dependent method type: parameter appears in the type of another parameter in the same section or an earlier one
      def bar[A](a: A)(implicit M:Foo[A], N:Monoid[M.B]): M.B = N.zero

そこで…

Aux Pattern

下記のような定義をします。

type Aux[A0, B0] = Foo[A0] { type B = B0 }

Type aliasを使いA0をFoo Aに、B0をBに、型メンバを型パラメーターにマッピングし、暗黙の型パラメータが持つ型パラメータ同士で依存関係を表現します。

trait Foo[A] {
  type B
  def foo(a: A): B
}

object Foo {
  type Aux[A0, B0] = Foo[A0] { type B = B0 }

  implicit val intAux: Foo.Aux[Int, String] = new Foo[Int]{ 
    type B = String 
    def foo(i:Int): String = i.toString
  } 
}

def foo[A, B](a: A)(implicit F: Foo.Aux[A, B]) = F.foo(a)
def foo[A, B](a: A)(implicit F: Foo.Aux[A, B], N:Monoid[B]): B = N.zero // 型メンバの参照ではなく、同じ型パラメータで与えているだけなのでコンパイル通る!

参考:
http://gigiigig.github.io/posts/2015/09/13/aux-pattern.html

RELATED POSTS