比较器是类型类吗?
我一直在阅读Scala中的类型类,并认为我对它有很好的把握,直到我记得Java的java.util.Comparator
。
如果我理解正确, Ordering
是类型类的原型示例。 我在Comparator
和Ordering
实例之间能够想到的唯一区别是比较器必然是显式的,而排序可以是,而且往往是隐含的。
Comparator
是类型类吗? 我得到(错误的?)印象,Java实际上没有类型类。 这是否意味着类型类需要能够隐式? 我认为类型类的隐式转换主要是语法糖 – 实际上很棒,它“简单地”给编译器足够的提示 – 我错过了什么?
下面的代码示例显示了Comparator
如何将排序操作添加到没有它的类型,而不必修改所述类型。
// Comparator used to retroactively fit the MyExample class with an ordering operation. public static class MyExampleComparator implements Comparator { public static final Comparator SINGLETON = new MyExampleComparator(); private MyExampleComparator() {} public int compare(MyExample a, MyExample b) { return a.value - b.value; } } // Custom type, its only purpose is to show that Comparator can add an ordering operation to it when it doesn't // have one to begin with. public static class MyExample { private final int value; public MyExample(int v) { value = v; } public String toString() { return Integer.toString(value); } } public static void main(String... args) { List list = new ArrayList(); for(int i = 0; i < 10; i++) list.add(new MyExample(-i)); // Sorts the list without having had to modify MyExample to implement an interface. Collections.sort(list, MyExampleComparator.SINGLETON); // Prints the expected [-9, -8, -7, -6, -5, -4, -3, -2, -1, 0] System.out.println(list); }
我不想特别谈论类型类,而是谈论Scala中的类型类模式 ; 原因是当你开始询问“什么是类型类”时,你最终得出的结论是它只是一个以特定方式使用的接口。
(在Haskell中,将特定构造称为类型类更有意义。)
类型类模式由三个基本部分组成(但为方便起见,通常还有一些部分)。 第一种是由单一类型参数化的接口,它在参数化类型上抽象出某种能力。 java.util.Comparator
是一个很好的例子:它提供了一个比较接口。 我们就这样用吧。
您需要的第二件事是使用该参数化的方法,您可以在Scala中使用简写符号指定:
// Short signature // v------------------- "We must be able to find a Comparator for A" def ordered[A: java.util.Comparator](a0: A, a1: A, a2: A) = { val cmp = implicitly[java.util.Comparator[A]] // This is the Comparator cmp.compare(a0, a1) <= 0 && cmp.compare(a1, a2) <= 0 } // Long signature version def ordered[A](a0: A, a1: A, a2: A)(implicit cmp: java.util.Comparator[A]) = { cmp.compare(a0, a1) <= 0 && cmp.compare(a1, a2) <= 0 }
好的,但你从哪里得到那个比较器? 这是第三个必要的部分。 默认情况下,Scala不会为您可能喜欢的类提供Comparator
,但您可以定义自己的类:
implicit object IntComp extends java.util.Comparator[Int] { def compare(a: Int, b: Int) = a.compareTo(b) } scala> ordered(1,2,3) res5: Boolean = true scala> ordered(1,3,2) res6: Boolean = false
现在您已经为Int
(隐式)提供了function,编译器将填充隐含参数以使其工作。 如果您尚未提供该function,则会出错:
scala> ordered("fish","wish","dish") :12: error: could not find implicit value for parameter cmp: java.util.Comparator[String] ordered("fish","wish","dish")
直到你提供该function:
implicit object StringComp extends java.util.Comparator[String] { def compare(a: String, b: String) = a.compareTo(b) } scala> ordered("fish","wish","dish") res11: Boolean = false
那么,我们是否将java.util.Comparator
称为类型类? 它当然与处理类型类模式的等效部分的Scala特征一样起作用。 因此,即使类型类模式在Java中也不能正常工作(因为您必须显式指定要使用的实例而不是隐式查找它),从Scala的角度来看, java.util.Comparator
是一个类型类什么都有。
术语类型类来自Haskell,因为它们是语言的一部分。 在scala中,它不是,它更像是一种模式,恰好在scala中有很多语言支持(主要是隐含)。 即使没有这种语法支持,模式也是有意义的,例如在java中,我会说Comparator
就是那种模式的一个典型例子(尽管在java中没有使用术语类型类)。
从面向对象的角度来看,模式包括Comparator
而不是Comparable
。 最基本的对象思想是在对象中有比较服务,比如class String implements Comparable
。 但是,提取它有很多优点:
- 您可以为无法更改其代码的类(例如,数组)提供服务
- 您可以提供不同的服务实现(有很多方法可以比较字符串(不区分大小写和很多语言相关的字符串)。人们可能按照他们的名字,年龄,等等进行排序。而且,您可能只想要一个订购逆转。
这两个原因足以在java中使用Comparable
,并在排序集合中使用它们(例如TreeSet
)保留Comparable
,因为它提供了一个方便的默认值(当你想要“默认”比较时不需要传递Comparator,并且它更容易调用(x.compareTo(y)而不是comparator.compare(x,y))。在scala中,带有implicits,这一点都没有引人注目(与java的互操作性仍然是实现Ordered / Comparable的一个原因在斯卡拉)。
键入类还有其他不那么明显的优点。 其中 :
- 类型类实现可用,即使您没有操作类型的实例,它也可能很有用。 考虑操作
sum(list)
。 它要求列表的元素有某种可用的附加function。 这可能在元素本身中可用。 假设他们可能是一些Addable[T]
与def add(other: T): T
。 但是如果你将空列表传递给sum,它应该返回列表类型类型的“零”(0表示整数,空字符串表示字符串……)。 有一个def zero: T
在Addable[T]
将是无用的,因为在那一刻,你没有Addable
。 但这适用于类型类,如Numeric
或Monoid
。 - 由于类型类是物化的(它们是对象而不是方法),因此它们是第一类,您可以将它们组合起来,转换它们。 一个非常简单的例子是反转Ordering(您也可以在Comparable上实现它,在java中可能在静态方法中)。 您可以组合
Int
和String
的顺序以在对(Int, String)
上定义排序,或者在T
上给出Ordering
,在List[T]
上构建排序。 Scala用implicits做了这个,但它在java中仍然有意义。
一个更复杂的例子:
// Comparison by the first comparator which finds the two elements different. public static Comparator lexicographic (final Comparator ... comparators) { return new Comparator () { public int compare(T t1, T t2) { for(comparator : comparators) { int result = comparator.compare(t1, t2); if (result != 0) return result; } return 0; } } }
(在scala中可能更简单,但同样,这在java中很有用)
还有一些小的缺点(在Java中比在scala中更多,但仍然)
- 您必须将类型类实例从方法传递给方法。 使用隐式参数或[T:Comparable]约束在scala中更容易,但是,如果不在调用站点,则必须在方法定义中编写某些内容,并且在运行时,必须传递参数。
- 所有内容都必须在编译时设置(即使在scala中它是隐式设置的。所以当你可以尝试
if(x is Comparable>) {do some sorting}
,这对于Comparator是不可能的。
没有java.util.Comparator是一个接口
public interface Comparator
比较函数,它对某些对象集合施加总排序。 可以将比较器传递给排序方法(例如Collections.sort或Arrays.sort),以便精确控制排序顺序。 比较器还可用于控制某些数据结构的顺序(例如有序集或有序映射),或者为不具有自然顺序的对象集合提供排序。