既存のクラスをScalazで定義されている型クラスのインスタンスにするの巻

この記事はScala Advent Calendar 2015(Adventar)の9日目の記事です。

Scalaには型クラスのための仕組みがあるぞ!

Scalaには、型クラスを実現するための仕組みとしてimplicit parameterという仕組みがあります。これとimplicit conversionを組み合わせることによって、オーバーロードやinterfaceでは実現できないようなオープンな演算の定義と安全性を両立させることができます。

このあたりの話は http://nekogata.hatenablog.com/entry/2014/06/30/062342 などを参照してください。

Scalazとかいう怖いライブラリもあるぞ!

Scalaの怖いライブラリの代表格として、Scalazというものがあり、ここでは様々な便利な型クラスが定義されています。たとえばそのなかのひとつに「Order型クラス」が存在します。

このOrder型クラスは、「順序があるよ」という性質を表した型クラスになっており、scala標準の型でも、Stringなど様々なクラスがこの型クラスのインスタンスにされています

また、Order型のインスタンスになることで、implicit conversion経由で "<" や ">" などの便利メソッドが使えるようになります

自分で手出しできないライブラリを型クラスのインスタンスにするぞ!

たとえば JodaTime を利用しているとき、AbstractInstantなインスタンス を > や < で比較できたら便利だなー、などと思ったとします。AbstractInstantな型をOrder型クラスのインスタンスにしてしまえば実現できそうですね。実際にやってみましょう。

まずはOrder型クラスの定義を見に行きます

見てのとおり、orderメソッドだけ実装されていないtraitがその実態です。ということは、orderメソッドを実装したAbstractInstantOrder[A <: AbstractPartial] extends Order[A]なオブジェクトへのimplicit conversionを定義してやれば、DateTimeなどのAbstractInstantのサブクラスのインスタンスを < や > で比較できるようになるはずです。(このへんの理屈がわからない場合は、しつこいようですが http://nekogata.hatenablog.com/entry/2014/06/30/062342 を参照してください)

自分でがんばってobject AbstractInstantOrder[A <: AbstractInstant] extends Order[A]を定義して、それへのimplicit conversionを定義してもいいのですが、Order.scalaを読んでいたらこんな便利なメソッドを見つけました。order[Nyan] {(a, b) => ???}としてやれば、new NyanOrder extends Order[Nyan]{ def order(a: Nyan, b: Nyan) = ??? }を返してくれる便利なやつです。こいつとimplicit conversionを組み合わせてAbstractInstantな型をOrder型クラスのインスタンスにしてやると、こんなかんじになります。

object JodaTimeOrder {
  implicit def JodaTimeInstantOrder[A <: AbstractInstant]: Order[A]  = order { (a, b) =>
    if (a.isBefore(b)) scalaz.Ordering.LT
    else if (a.isEqual(b)) scalaz.Ordering.EQ
    else scalaz.Ordering.GT
  }
}

これで、JodaTimeのAbstractInstantなクラスすべてを、Order型クラスのインスタンスにすることができました!使うときには、以下のようなかんじになります。

import JodaTimeOrder._
import scalaz.Scalaz._

if (dateTimeInstanceA < dateTimeInstanceB) {
  // Bのほうが後の日時
} else {
  // Bのほうが後の日時ではない
}

自分で書き換えることのできないようなクラスも、ちょっとしたグルーコードを書いてやることで型クラスのインスタンスのしてやることができましたね!

最後に

既存のクラスをScalazの型クラスのインスタンスにする例を見てみました。Scalazのコードを読みながら「なるほどこうやればいいんだな」みたいなかんじでやっていけばできることではあるのですが、「わざわざコード読まなくてもここに書いてあるよ」みたいな情報を知っているひとがあればぜひ教えてください。