読者です 読者をやめる 読者になる 読者になる

続・結局型クラスって何がうれしいのっていう話

今回は時間がないので簡単に見ます。

型クラスによってアドホック多相が実現されるとどんな柔軟なことができるのかという例を見ましょう。

たとえば、List#max について見て見ましょう。これ、要素が String だったら辞書順での最大値を返すし、Int だったら数的な意味での最大値を返しますよね。もし「そもそも比べられない型」が要素に入ってたらコンパイルエラーになります。

class Nyan(val value: String) //比べることのできない型

println(List(1, 2, 3).max) // => 3
println(List("a", "b", "c").max) // => c
println(List(new Nyan("a"), new Nyan("b"), new Nyan("c")).max) // => error:No implicit Ordering defined for this.Nyan.

おっこれはまさにアドホック多相ですね。そして、API ドキュメントを読むと、これのFull Signature は def max[B >: A](implicit cmp: Ordering[B]): A になっています。あっこれは完全に型クラスですね。

では例によって動きを詳しく見てみましょう。Aはどこから出てきたかというと List の定義 class List[+A] からです。となると、B は、[B >: A] より、Listの要素と同じクラスか、その親クラスに限定されることになりますね。ではこのとき List(1, 2, 3).max とすると、何が起こるでしょうか。まず A が Int に確定しますね。これによって、B は Int かその親クラスに確定します。すると implicit cmp の型は Ordering[B :> Int]となりますね。 APIドキュメントを引くとIntOrdering extends Ordering[Int] という trait が見つかりました。これで implicit cmp の型は IntOrdering に確定です。ここまでは今までさんざん見てきたのと同じ理屈ですね。

では新しく作った Nyan を Ordering 型クラスのインスタンスにしてみましょう。Ordering[Nyan] 型の implicit な値を定義してあげればよかったですね。

class Nyan(val value: String) {
  override def toString = s"Nyan(${value})" // 表示のため
}

implicit val nyanOrdering = new Ordering[Nyan] {
  def compare(a: Nyan, b:Nyan) = implicitly[Ordering[String]].compare(a.value, b.value)
}

println(List(1, 2, 3).max) // => 3
println(List("a", "b", "c").max) // => c
println(List(new Nyan("a"), new Nyan("b"), new Nyan("c")).max) // => Nyan(c)

おおー!!List#maxがNyanに対応した!!!!型クラス、めっちゃ便利なのでは?ということがわかってきたかと思います。

あっ、しれっと implicitly[Ordering[String]] とかいう今まで見たことのない新要素が登場していますが、これは「context bound」と呼ばれる記法で、「Ordering[String] 型の implicit な値を探してきてここに入れてください」という意味です。今回は Nyan の中の値が文字列なので、Ordering[String] の compare メソッドに処理を委譲したかったわけですが、特別なことはなにもせずに List("a", "b", "c").maxが呼べるということは、すでに Ordering[String] 型の implicit な値はこのスコープに存在しているわけで、だったらそれ使っちゃえばいいよね、ということでここで context bound を活用してみました。今APIリファレンス改めて引いたら Ordering.String ってのがあったので、Ordering.String.compare でもかまわないと思います。