はじめに
この記事は型クラスの便利さについて分かち合う記事です。
理解が目的ではないので、なんとなくええやんと思ってもらえれば十分です。
型クラスのイメージ
型クラスとは主に関数型言語に実装されている機能の一つです。(※正確には知りません)
型クラスがあるとこんな関数を作ることができます。
※サンプルコードは全て
scala
です。
def plus[A](left: A, right: A): A
どんな型でも足せてしまう関数・・・何だか良く分かりませんが凄そうですね!
どんな型でも足せてしまう関数の作り方
まず型クラス Plus[A]
を作ります。
ソースのイメージは
scala
です
trait Plus[A] {
def plus(left: A, right: A): A
}
そしてどんな型でも足せてしまう関数 plus[A](A, A): A
の実装を書きます。
def plus[A: Plus](left: A, right: A): A = implicitly[Plus[A]].plus(left, right)
完成です!
さすがにソースコード少なすぎでは?・・・何だか良く分かりませんが凄そうですね!
実行してみる
scala> plus(1, 2)
<console>:13: error: could not find implicit value for evidence parameter of type Plus[Int]
plus(1, 2)
^
scala>
・・・コンパイルエラーで動きませんでした。どうやら完成というのは嘘だったようです。
エラーメッセージによると、 型クラス Plus[Int]
の値が見つからなかったみたいですね。
Plus[Int]
を作る
implicit val intPlus = new Plus[Int] {
def plus(left: Int, right: Int) = left + right
}
Plus[Int]
を作ってみました。Plus[A]
はA
の足し方を表しているようですね。
するとどうでしょう。
scala> plus(1, 2)
res1: Int = 3
今度はInt
を足すことができました。
関数には手を加えていないので、関数が完成していたというのは嘘ではなかったようです。
もちろんPlus[String]
やPlus[Foo]
を定義すれば、String
やFoo
も足すことができるようになります。
implicit val stringPlus = new Plus[String] {
def plus(left: String, right: String) = left + right
}
scala> plus("foo", "bar")
res3: String = foobar
勘のいい人は気づいているかもしれませんが「どんな型でも足せる関数」plus[A](A, A): A
は、与えられた引数の型と一致するPlus[A]
を利用する事で実現されています。
適切なPlus[A]
を探し出す仕事はコンパイラがコンパイルタイムにやってくれるので、あのソースコードだけで成立できていたのです。
利点をもう少し具体的に挙げてみました。
- 適切な
Plus[A]
を探し出す処理はコンパイラが頑張ってくれるので実装が不要 - コンパイルタイムに適切な
Plus[A]
を探し出してくれるので、ランタイムにPlus[A]
が見つからずにエラーになる可能性がない - つまり適切な
Plus[A]
が探し出される事のテストを書く必要もない
要はコンパイラがせっせと我々の代わりに働いてくれる訳です。
個人的には一種の自動化のように思えて気に入っています。実際に手を動かさなくて済むので楽ですし、良くないですか?
様々な型クラス
実は既に多くの素晴らしい型クラスが頭の良い人たちによって考案され公開されています。それらを提供しているライブラリの中でもscala
ではscalaz
やcats
がメジャーでしょうか。
例えば、今回の「A
とA
の足し方」の他に「A
の空の値」が定義されている型クラスの場合はどうでしょう?
どんな型のリストでも合計値を導けるsum[A](items: List[A]): A
のような関数も作ることができたり、
どんな型でもn
倍の値を導けるmultiply(value: A, n: Int): A
のような関数を作ることができます。
ちなみに
sum[A](items: List[A]): A
は、さらに一般化する事でList
を取り払いsum[M[_], A](xs: M[A]): A
とする事もできるらしいです。
このような関数の実装を可能にする型クラスはMonoid[A]
として提供されていています。
scalaz
やcats
は型クラスを利用した関数も多く提供されているので、インスタンスを準備するだけで良かったりします。
これはかなり簡単な部類なので、興味がある人は面白い発見があると思うのでぜひ他の型クラスについても調べてみてください。
sum[A](items: List[A]): A
やmultiply(value: A, n: Int): A
が実現できる理由が気になる人はMonoid
でググってみましょう!