2012年6月2日土曜日

わたしのScalazプログラミング

Scalaはオブジェクト指向言語なのですよ。
知ってました?

重要:これから書くコードはとてもメニアックなので参考にしないようにして下さい。

今日は有理数を定義してみます。


case class Rational(n: Int, d: Int)
view raw rational1.scala hosted with ❤ by GitHub

加算と減算を追加してみる。

case class Rational(n: Int, d: Int) {
def +(r: Rational) = Rational(n * r.d + r.n * d, d * r.d)
def -(r: Rational) = Rational(n * r.d - r.n * d, d * r.d)
}
view raw rational2.scala hosted with ❤ by GitHub

さて、この2つのメソッド、とても似ていますよね。
抽象化してしまいます。

case class Rational(n: Int, d: Int) {
def op(f: (Int, Int) => Int)(r: Rational) =
Rational(f(n * r.d, r.n * d), d * r.d)
lazy val + = op(_ + _)_
lazy val - = op(_ - _)_
}
view raw rational3.scala hosted with ❤ by GitHub

さて、今日の本題はここからです。
この加算と減算を他の数値型に対応させたいとします。
普通はこのようにオーバーロードを使いますよね。

case class Rational(n: Int, d: Int) {
def +(r: Rational) = Rational(n * r.d + r.n * d, d * r.d)
def +(i: Int) = Rational(n + i * d, i * d)
}
view raw rational4.scala hosted with ❤ by GitHub

しかし、+や-はメソッド型ではなく、値型なのでオーバーロードは使用できません。
そこで、オーバーロードを使わず、UnionTypesを使って定義します。

case class Rational(n: Int, d: Int) {
def +[A](a: A)(implicit ev: A ∈ t[Rational]#t[Int]) =
a match {
case Rational(nn: Int, dd: Int) => Rational(n * dd + nn * d, d * dd)
case i: Int => Rational(n + i * d, i * d)
}
}
view raw rational5.scala hosted with ❤ by GitHub

これでもまだ問題があります。
Scalaでは、型パラメータをとる関数を値として定義できません。
そこで、関数のようにふるまう型を自分で定義します。

case class Rational(n: Int, d: Int) {
case class Function(f: (Int, Int) => Int) {
def apply[A](a: A)(implicit ev: A ∈ t[Rational]#t[Int]) = a match {
case Rational(nn: Int, dd: Int) => Rational(f(n * dd, nn * d), d * dd)
case i: Int => Rational(f(n, i * d), i * d)
}
}
lazy val + = Function(_ + _)
lazy val - = Function(_ - _)
}
view raw rational6.scala hosted with ❤ by GitHub

これで+と-の演算を抽象化し、さらに多相に対応しましたね。
めでたしめでたし。

0 件のコメント:

コメントを投稿