Ordered and Ordering
Scalaには2値の比較を扱うtraitがあります。一つはComparable[T]を継承しているOredered[T]、もう一つはComparator[T]を継承しているOrdering[T]です。
Comparable[T]はjava.util.Comparable、Comparator[T]はjava.util.Comparatorに対応しており、前者はcompareTo(t)を、後者はcompare(t1, t2)を持っています。つまり自分と他のオブジェクトを比較するか、2つのオブジェクトを比較するか、という点が異なります。
Ordered[T]を扱っていると、たまにOrderingへの変換に失敗した、というコンパイルエラーが出ることがあります。これがどうも気になったので、今回はOrderedとOrderingの関係を調べてみました。
A comparison of Ordered[T]
2つのOrdered[T]を比較するメソッドを作成してみたいと思います。そもそもOrdered[K]はcompare(k: K)というメソッドを持っているので、それを使えば良いはず。
def compareAB[K](a: Ordered[K], b: Ordered[K]): Int = {
a.compare(b)
}
しかしこのメソッドはコンパイルエラーになります。
エラーの原因は、Ordred[K]#compare(k: K)が引数に取るのはKであって、Ordered[K]ではないことにあります。Ordered[K]はKの親クラスではないので、型に互換性がありません。しかしcompareメソッドを持っているのはOrdered[K]なので、このメソッドを使うにはOrdered[K]の型で扱う必要があります。以上より、compareABメソッドの定義は以下となります。
def compareAB[K](a: Ordered[K], b: K): Int = {
a.compare(b)
}
2値の比較であるにも関わらず、2値の型が違うのは違和感があります。
A way to sorting Ordered[K]
先ほどの例は2値の比較だったので引数の型の調整は簡単に行えました。しかし、これが任意の要素を対象とした場合、どうなるのでしょうか? 特に方法を思いつけなかったので、sortを行うAPIを見てみたところ、scala.util.Sortingに以下のメソッドが定義されていました。
def quickSort[K: Ordering](a: Array[K]) { sort1(a, 0, a.length) }
[K: Ordering]という箇所の意味が分からず調べてみたところ、これはContext Boundと呼ばれるシンタックスシュガーでした。
View Bound/Context Bound | ブログ.武田ソフト.jp
これをシンタックスシュガーを適用せずに記述すると以下になります。
def quickSort[K](a: Array[K])(implicit arg0: Ordering[K]): Unit
SortingのAPIリファレンスにもこの形式で載っています。前記のシンタックスシュガーを使っているのはソースコードの方です。
とはいえ丸括弧が2つ連なっており、そんなに分かりやすくなった感も無いのですが、要はソート対象のArray以外に、Ordering[K]という暗黙パラメータを指定する仕組みになっています。
Orderingは前記のとおり、2値を比較するメソッドcompareを保持している為、quickSortメソッドの中ではこの暗黙パラメータを使って値の比較を行うことになっています。
Where has this implicit parameter defined?
Ordering[K]はどこで定義されているのでしょうか? 試しに、以下のようなコードを書いてみました。
OrderedCompareTest.scala:
1package net.wrap_trap.scala.examples
2
3import java.util.Comparator
4
5object OrderedCompareTest {
6 def main(args : Array[String]) : Unit = {
7
8 System.out.println("called main")
9 System.out.println(
10 compareAB(new OrderedClass(2), new OrderedClass(1)))
11 }
12
13 class OrderedClass(val n: Int) extends Ordered[OrderedClass] {
14 System.out.println("called new OrderedClass")
15 def compare(that: OrderedClass) = this.n - that.n
16 }
17
18 // View Bound from scala.math.LowPriorityOrderingImplicits
19 implicit def ordered[T <% Comparable[T]]: Ordering[T] = new Ordering[T] {
20 /* = implicit def ordered[T](implicit f: T => Comparable[T]): Ordering[T] = new Ordering[T] { */
21 System.out.println("called ordered. f: " + implicitly)
22 def compare(x: T, y: T): Int = {
23 x.compareTo(y)
24 }
25 }
26
27 // Context Bound
28 def compareAB[K:Ordering](a: K, b: K): Int = {
29 /* = def compareAB[K](a: K, b: K)(implicit comp: Ordering[K]): Int = { */
30 val comp = implicitly
31 System.out.println("called compareAB comp:" + comp)
32 comp.compare(a, b);
33 }
34}
Context Boundを使っていたquickSortのメソッド定義を、compareABという2値を比較するメソッドに適用してみました(上記コードの最下部)。
このメソッドの中で、暗黙パラメータをimplicitlyメソッドで取得しコンソールに出力してみたところ、Ordering[K]はscala.math.LowPriorityOrderingImplicitsで定義されていました。ソースコードは以下となります。
1trait LowPriorityOrderingImplicits {
2 /** This would conflict with all the nice implicit Orderings
3 * available, but thanks to the magic of prioritized implicits
4 * via subclassing we can make `Ordered[A] => Ordering[A]` only
5 * turn up if nothing else works. Since `Ordered[A]` extends
6 * `Comparable[A]` anyway, we can throw in some Java interop too.
7 */
8 implicit def ordered[A <% Comparable[A]]: Ordering[A] = new Ordering[A] {
9 def compare(x: A, y: A): Int = x compareTo y
10 }
11 implicit def comparatorToOrdering[A](implicit cmp: Comparator[A]): Ordering[A] = new Ordering[A] {
12 def compare(x: A, y: A) = cmp.compare(x, y)
13 }
14}
2つの暗黙的な型変換を行うメソッドが定義されていますが、今回はorderedの方が適用されていました。暗黙的な型変換は定義されたメソッドのスコープが近い方が優先されるはずなので、先ほどのコード上で同じメソッドを定義し、そちらが暗黙裡に呼ばれるようにしています。
そしてこのメソッドも[A <% Comparable[A]]という部分が理解できませんでした。調べてみたところ、こちらはView Boundと呼ばれるシンタックスシュガーでした。詳しくは前記の「ブログ.武田ソフト.jp」のページを参照してください。
要は、AがComparable[A]に変換可能である場合、AをOrdering[A]に変換する暗黙的な型変換メソッドを定義しています。
今回の場合、Kの親クラスはOrdered[K]で、Ordered[K]の親traitはComparable[K]なので、KはComparable[K]に変換可能であることから、この条件が成り立ちます。このメソッドによって暗黙パラメータのOrdering[K]が提供されていたことになります。
ということで、Comparable[K]からOrdering[K]が生成され、それをcompareABが使用することで、K(extends Order[K])の2値を比較することができました。
A comparison of K(not Ordered[K])
もしKがOrdered[K]を継承していないとしたら、LowPriorityOrderingImplicits#orderedが適用されず、Ordering[K]が生成されない為、compareABメソッドは実行できないことになります。
これを確かめる為、前記のコードをOrdered[K]を継承していないクラスに適用してみました。
OrderedCompareTest2.scala:
1package net.wrap_trap.scala.examples
2
3import java.util.Comparator
4
5object OrderedCompareTest2 {
6 def main(args : Array[String]) : Unit = {
7
8 System.out.println("called main")
9 // compile error when disabling nonOrderedClassToComparable Method.
10 // No implicit Ordering defined for
11 // net.wrap_trap.scala.examples.OrderedCompareTest2.NonOrderedClass.
12 System.out.println(
13 compareAB(new NonOrderedClass(4), new NonOrderedClass(3)))
14 }
15
16 class NonOrderedClass(val n: Int) {}
17
18 implicit def nonOrderedClassToComparable(from: NonOrderedClass): Comparable[NonOrderedClass] = {
19 new Comparable[NonOrderedClass] {
20 def compareTo(b: NonOrderedClass): Int = {
21 from.n - b.n
22 }
23 }
24 }
25
26 // View Bound from scala.math.LowPriorityOrderingImplicits
27 implicit def ordered[T <% Comparable[T]]: Ordering[T] = new Ordering[T] {
28 /* = implicit def ordered[T](implicit f: T => Comparable[T]): Ordering[T] = new Ordering[T] { */
29 def compare(x: T, y: T): Int = {
30 x.compareTo(y)
31 }
32 }
33
34 // Context Bound
35 def compareAB[K:Ordering](a: K, b: K): Int = {
36 /* = def compareAB2[K](a: K, b: K)(implicit comp: Ordering[K]): Int = { */
37 val comp = Ordering[K]
38 System.out.println("called compareAB comp:" + comp)
39 comp.compare(a, b);
40 }
41}
すると以下のコンパイルエラーがでました。
No implicit Ordering defined for
net.wrap_trap.scala.examples.OrderedCompareTest2.NonOrderedClass.
やはりComparable[K]に変換できないことが原因のようです。
Comparable[K]に変換できるように、暗黙的な型変換メソッド「nonOrderedClassToComparable」を定義したところ、コンパイルがとおり、Ordered[K]の時と同じ結果になりました。
Conclusion
今回はOrdered[K]とOrdering[K]の関係について調べてみました。元々はHaskellの赤黒木のコードをScalaに移植するのが事の始まりなのですが、implicitな型変換、パラメータやシンタックスシュガーによって簡素化された構文が理解できず大分苦労しています。今回の件で暗黙パラメータ、View BoundとContext Boundは理解できたので、ここから先はスラスラ行く、と思いたい…。