我如何形成scala SortedMaps的联合?

时间:2009-12-21 03:39:01

标签: scala scala-2.8 scala-collections sortedmap

(我正在使用Scala nightlies,并在2.8.0b1 RC4中看到相同的行为。我是Scala的新手。)

我有两个SortedMap我希望组成联盟。这是我想要使用的代码:

import scala.collection._

object ViewBoundExample {
    class X
    def combine[Y](a: SortedMap[X, Y], b: SortedMap[X, Y]): SortedMap[X, Y] = {
        a ++ b
    }
    implicit def orderedX(x: X): Ordered[X] = new Ordered[X] { def compare(that: X) = 0 }
}

这里的想法是'隐式'语句意味着X可以转换为Ordered[X] s,然后将SortedMap组合成另一个SortedMap是有道理的,而不仅仅是一张地图。

当我编译时,我得到了

sieversii:scala-2.8.0.Beta1-RC4 scott$ bin/scalac -versionScala compiler version
2.8.0.Beta1-RC4 -- Copyright 2002-2010, LAMP/EPFL

sieversii:scala-2.8.0.Beta1-RC4 scott$ bin/scalac ViewBoundExample.scala
ViewBoundExample.scala:8: error: type arguments [ViewBoundExample.X] do not
    conform to method ordered's type parameter bounds [A <: scala.math.Ordered[A]]
        a ++ b
          ^
one error found

如果该类型参数绑定为[A <% scala.math.Ordered[A]]而不是[A <: scala.math.Ordered[A]],我的问题似乎就会消失。不幸的是,我甚至无法找出“命令”生活的方法!任何人都可以帮我追踪它吗?

如果不这样做,我打算如何制作两个SortedMap s的联合?如果我删除了返回类型的组合(或将其更改为Map),一切正常 - 但是我不能依赖返回的排序!

2 个答案:

答案 0 :(得分:5)

目前,您使用的是scala.collection.SortedMap特征,其++方法继承自MapLike特征。因此,您会看到以下行为:

scala> import scala.collection.SortedMap
import scala.collection.SortedMap

scala> val a = SortedMap(1->2, 3->4)
a: scala.collection.SortedMap[Int,Int] = Map(1 -> 2, 3 -> 4)

scala> val b = SortedMap(2->3, 4->5)
b: scala.collection.SortedMap[Int,Int] = Map(2 -> 3, 4 -> 5)

scala> a ++ b
res0: scala.collection.Map[Int,Int] = Map(1 -> 2, 2 -> 3, 3 -> 4, 4 -> 5)

scala> b ++ a
res1: scala.collection.Map[Int,Int] = Map(1 -> 2, 2 -> 3, 3 -> 4, 4 -> 5)

++的返回结果的类型是Map[Int, Int],因为这是++对象的MapLike方法返回的唯一类型。似乎++保留了SortedMap的排序属性,我想这是因为++使用抽象方法来进行连接,并且这些抽象方法被定义为保持顺序的地图。

为了拥有两个有序地图的联合,我建议你使用scala.collection.immutable.SortedMap

scala> import scala.collection.immutable.SortedMap
import scala.collection.immutable.SortedMap

scala> val a = SortedMap(1->2, 3->4)
a: scala.collection.immutable.SortedMap[Int,Int] = Map(1 -> 2, 3 -> 4)

scala> val b = SortedMap(2->3, 4->5)
b: scala.collection.immutable.SortedMap[Int,Int] = Map(2 -> 3, 4 -> 5)

scala> a ++ b
res2: scala.collection.immutable.SortedMap[Int,Int] = Map(1 -> 2, 2 -> 3, 3 -> 4, 4 -> 5)

scala> b ++ a
res3: scala.collection.immutable.SortedMap[Int,Int] = Map(1 -> 2, 2 -> 3, 3 -> 4, 4 -> 5)

SortedMap特征的此实现声明了++方法,该方法返回SortedMap

现在,您对类型界限的问题有几个答案:

  • Ordered[T]是一个特征,如果在一个类中混合,它指定可以使用<>=,{{1 },>=。您只需要定义抽象方法<=,它为compare(that: T)返回-1,为this < that返回1,为this > that返回0 。然后基于this == that

  • 的结果在特征中实现所有其他方法
  • compare表示Scala中绑定的视图。这意味着类型T <% U或者是T的子类型,或者可以通过范围中的隐式转换隐式转换为U。如果您放置U但不放<%,则代码有效,因为<:不是X的子类型,但可以使用{{隐式转换为Ordered[X] 1}}隐式转换。

修改:关于您的评论。如果您使用的是Ordered[X],那么您仍在编程接口而不是实现,因为不可变的OrderedX被定义为scala.collection.immutable.SortedMap。您可以将其视为SortedMap的更专业特征,它提供其他操作(如返回trait的{​​{1}})和不可变属性。这符合Scala哲学 - 更喜欢不变性 - 因此我没有看到使用不可变scala.collection.SortedMap的任何问题。在这种情况下,您可以保证结果将明确地进行排序,并且由于集合是不可变的,因此无法更改。

尽管如此,我仍然觉得奇怪的是,++没有提供SortedMap方法,因此会返回SortedMap。我所做的所有有限测试似乎都表明,两个scala.collection.SortedMap的串联结果确实产生了一个保存已排序属性的映射。

答案 1 :(得分:4)

作为Scala的初学者,你选择了一个难以破解的坚果! : - )

好的,简短的游览,不要指望现在完全理解它。首先,请注意问题发生在方法++上。在搜索其定义时,我们在特征MapLike处找到它,接收IteratorTraversable。由于ySortedMap,因此它是使用的Traversable版本。

请注意,在其广泛的类型签名中,传递了CanBuildFrom。它是隐式传递的,因此您通常不需要担心它。但是,要了解发生了什么,这次你会这样做。

您可以通过点击++定义中的CanBuildFrom或过滤来找到它。正如Randall在评论中提到的那样,scaladoc页面的左上角有一个未标记的空白字段。你只需要点击那里并键入,它就会返回你输入的内容的匹配项。

因此,在ScalaDoc上查找特征CanBuildFrom并选择它。它有大量的子类,每个子类负责构建特定类型的集合。搜索并单击子类SortedMapCanBuildFrom。这是从SortedMap生成Traversable所需的对象类。请注意它接收隐式Ordering参数的实例构造函数(类的构造函数)。现在我们越来越近了。

这一次,使用过滤器过滤器搜索Ordering。其伴随对象(单击名称中的小“o”)将隐藏一个将生成Ordering的隐式对象,因为将检查协同对象是否为该类生成实例或转换。它在特征LowPriorityOrderingImplicits内定义,对象Ordering扩展,查看它会看到方法ordered[A <: Ordered[A]],它将产生所需的Ordering ...如果只是没有问题,或者会产生它。

有人可能会认为从XOrdered[X]的隐式转换就足够了,就像我在仔细研究之前所做的那样。但是,这是对象的转换,而ordered期望接收类型,它是Ordered[X]的子类型。虽然可以将X类型的对象转换为Ordered[X]类型的对象,但X本身不是Ordered[X]的子类型,因此无法传递作为ordered的参数。

另一方面,您可以创建隐式val Ordering[X],而不是def Ordered[X],您将解决问题。具体做法是:

object ViewBoundExample {
    class X
    def combine[Y](a: SortedMap[X, Y], b: SortedMap[X, Y]): SortedMap[X, Y] = {
        a ++ b
    }
    implicit val orderingX = new Ordering[X] { def compare(x: X, y: X) = 0 }
}

我认为大多数人对Ordered / Ordering的初步反应必定是困惑之一:为什么要为相同的事情提供课程?前者扩展java.lang.Comparable,而后者扩展java.util.Comparator。唉,compare的类型签名几乎总结了主要区别:

def compare(that: A): Int     // Ordered
def compare(x: T, y: T): Int  // Ordering

使用Ordered[A]要求A延长Ordered[A],这需要一个人能够修改 A'定义,或传递一个可以将A转换为Ordered[A]的方法。 Scala完全能够轻松地完成后者,但是在比较之前你来转换每个实例。

另一方面,使用Ordering[A]需要创建单个对象,如上所述。使用它时,只需将A类型的两个对象传递给compare - 在此过程中不会创建任何对象。

因此,有一些性能提升,但Scala偏好Ordering优于Ordered的原因更为重要。再次查看Ordering的伴随对象。您会注意到,在那里定义的许多Scala类有几个含义。您可能还记得我之前提到的,T的伴随对象内部将会搜索隐含的类T,而这正是发生的事情。

也可以Ordered完成。但是,这是关键点,这意味着支持OrderingOrdered的每个方法都会失败!那是因为Scala会寻找隐含的功能,并且会找到两个:Ordering一个,Ordered一个。由于无法确定您想要的是什么,Scala会放弃错误消息。因此,必须做出选择,Ordering还有更多的事情要做。

Duh,我忘了解释为什么签名没有定义为ordered[A <% Ordered[A]],而不是ordered[A <: Ordered[A]]。我怀疑这样做会导致我之前提到过的双重隐含失败,但是我会问那些真正做过这个问题的人,并且这个特殊方法是否有问题会产生双重隐含问题。