Scalaで型クラス?

久しぶりにいくつか記事を書いたけど,

tkhshyt.hatenablog.com

tkhshyt.hatenablog.com

tkhshyt.hatenablog.com

そもそもScalaで型クラスをシミュレートする方法は,Scalaに関する基本的な知識なのだろうか?
(ここで挙げた記事ではまだ型クラスを使っていないが,後々 Bifunctor とか (mono)Functor が出てくる(予定))

型クラス

Haskellを触ってきてください.

Scala で型クラスをシミュレートする

さて,Scala で型クラスをシミュレートする方法を見てみる.
ただ,今回は型クラス制約が implicit parameter に対応するとか,そういう話はしないで,もっと基本的な部分.
その話は,次の機会にでも.

Scala で型クラスをシミュレートする方法を見るには,実際に一つ型クラスとそのインスタンスをいくつか作ってみるのが早い.
恐らく Monoid が一番簡単だと思われるが,今回は Functor を作ってみる.
Functor を選ぶ理由としては,後に説明するが Function1Functorインスタンスにするには,少しばかり工夫が必要で,この記事を書いた趣旨はそこにある.

ネタバレ(?)をしておくと,以降の内容は

独習 Scalaz — Unapply

に書かれています.

Functor

Functor については Haskell を(ry
今回は,Functor が何か?ではなく,ScalaFunctor を実装するとどうなるのかに重点を置きたいので,この辺に関する基本的な知識は常識ということで.

さっそく,ScalaFunctor を書いてみましょう.

trait Functor[F[_]] {

  def map[A, B](fa: F[A])(f: A => B): F[B]
}

こんな感じになります. 自分のように,Haskell を書けないけど読める人のために Haskell の定義を載せておきます.

class Functor f where
  fmap :: (a -> b) -> f a -> f b

Scala の場合は,引数の順番が異なっていることに注意して下さい.
次にインスタンスを定義してみましょう.
準備運動として,ListFunctorインスタンスとしてみます.

object FunctorInstance {
  implicit val listFunctor: Functor[List] =
    new Functor[List] {

      def map[A, B](fa: List[A])(f: A => B): List[B] =
        fa.map(f)
    }
}

グローバルに変数を宣言することはできないので,FunctorInstance オブジェクトの中に書きました*1
map の実装は,ScalaList には元から map があるためそれを利用することにします.
これは Haskell だと,

instance Functor [] where
    fmap = map

こうらしいです(初めて知った).

さて,Haskell ならインスタンスを定義するだけで望みのものが手に入りますが,Scala はそうもいきません.
Scalaオブジェクト指向言語なので,我々が欲っするのは Haskellfmap (*2) [1, 2, 3] のように,グローバル(?)な関数として map を呼ぶのではなく, List(1, 2, 3).map(_ * 2) のようにメソッドとして呼びたいと思うのが自然でしょう.
そのためには,少しばかり梃入れが必要です.
以下のようなクラスとオブジェクト*2を追加します.

final class FunctorOps[F[_], A](val self: F[A])(implicit val F: Functor[F]) {

  final def map[B](f: A => B): F[B] = F.map(self)(f)
}

object ToFunctorOps {
  implicit def ToFunctorOps[F[_], A](v: F[A])(implicit F0: Functor[F]) =
    new FunctorOps[F, A](v)
}

さて,これで準備が整いました.

scala> import FunctorInstance._
import FunctorInstance._

scala> import ToFunctorOps._
import ToFunctorOps._

scala> List(1, 2, 3).map(_ * 2)
res0: List[Int] = List(2, 4, 6)

これだと,今回定義した map が呼ばれているのか Listmap が呼ばれているのかわからない.
別のインスタンスを定義してみましょう.
Function1(一引数関数)も Functor になると誰かが言っていた気がします.
さっそく定義してみる.

object FunctorInstance {
  // 省略

  implicit def function1Functor[T]: Functor[T => ?] =
    new Functor[T => ?] {

      def map[A, B](fa: T => A)(f: A => B): T => B =
        t => f(fa(t))
    }
}

こんな感じにすればよいでしょう.
ここでは kind-projector というコンパイラプラグインを利用しています.

github.com

さて,この実装が正しく動くならば ((_: Int) * 2).map((_: Int) + 1) (2) を実行すると,5 が得られるはず.

scala> ((_: Int) * 2).map((_: Int) + 1) (2)
<console>:18: error: value map is not a member of Int => Int
       ((_: Int) * 2).map((_: Int) + 1) (2)

Oops. Int => Intmap なんてメソッドは無いと言われてしまった.
これは良く考えてみると当然で,FunctorインスタンスFunctorOps に変換するための暗黙的型変換を次のように書いていた.

implicit def ToFunctorOps[F[_], A](v: F[A])(implicit F0: Functor[F]) =
    new FunctorOps[F, A](v)

ToFunctorOps[F[_], A] を見ると F[_] というように,F は型変数を一つだけ受けとるような型となっているが, Function1 の型シグネチャFunction1[A, B] というようになっており,FFunction1 が来ることができない.
これは困った.そこで,この問題を解決するために Unapply と呼ばれるものを使おうと思う.

trait Unapply[TC[_[_]], MA] {

  type M[_]

  type A

  def TC: TC[M]

  def apply(ma: MA): M[A]
}

object Unapply {

  implicit def unapplyMAB2[TC[_[_]], M0[_, _], A0, B0](implicit TC0: TC[M0[A0, ?]]) =
    new Unapply[TC, M0[A0, B0]] {
      type M[X] = M0[A0, X]
      type A = B0
      def TC = TC0
      def apply(ma: M0[A0, B0]) = ma
    }
}

さらに ToFunctorOps に以下のコードを追加する.

object ToFunctorOps {
 implicit def ToFunctorOpsUnapply[FA](v: FA)(implicit F0: Unapply[Functor, FA]) =
   new FunctorOps[F0.M,F0.A](F0(v))(F0.TC)
}

さて,これにより何が起こるのか?
コンパイラは,((_: Int) * 2).map((_: Int) + 1) (2) に出会ったとき,どうにかして map が呼び出せる形に変換できないが探そうとします.
先程までは,それに失敗していたわけですが,UnapplyToFunctorOpsUnapply を追加したことで Unapply[Functor, Int => Int] が暗黙的に定義されている(暗黙的に変換できる)か探すようになります.
すると,unapplyMAB2 を使って,

implicit def unapplyMAB2[Functor, Function1[_, _], Int, Int](implicit TC0: Functor[Function1[Int, ?]]) =
  new Unapply[Functor, Function1[Int, B0]] {
    type M[X] = Function1[Int, X]
    type A = Int
    def TC = TC0
    def apply(ma: Function1[Int, Int]) = ma
  }
  • TC = Functor
  • M0 = Function1
  • A0 = Int
  • B0 = Int

とすれば,全てが上手く行きそうで,implicit TC0: Functor[Function1[Int, ?]]Functorインスタンスとして定義していたもの(implicit def function1Functor[T]: Functor[T => ?])を使えば暗黙的に変換できるため,この制約も問題無さそうだ.

これにより,

implicit def ToFunctorOpsUnapply[FA](v: FA)(implicit F0: Unapply[Functor, FA]) =
   new FunctorOps[F0.M,F0.A](F0(v))(F0.TC)

implicit def ToFunctorOpsUnapply[Function1[Int => Int]](v: Function1[Int => Int])(implicit F0: Unapply[Functor, Function[Int => Int]]) =
   new FunctorOps[Function1[Int,?],Int](F0(v))(F0.TC)

となり,先程の List の場合は暗黙的に渡されていた implicit val F: Functor[F] を 明示的に F0.TC として渡すことで,Function1[Int, Int] に対して map メソッドを呼び出せる形(FunctorOps)に変形することができる.

さて,本当にできるようになったか試す.

scala> ((_: Int) * 2).map((_: Int) + 1) (2)
res0: Int = 5

問題無さそう.

結論

Scala型推論(?)は弱くて面倒だね.

この話題に関する issue か何かがどっかにあった気がするけど,どこにあるのか忘れた.

*1:本当はトレイトの方がよい

*2:本当はトレイトの方がよい