从Int到Ordered的隐式转换问题

时间:2011-04-04 14:14:07

标签: scala

这是Scala中左派堆的实现。

package my.collections

sealed abstract class Heap[E](implicit val ordering:Ordering[E])  {

  import ordering._

  def empty: Heap[E] = Heap.empty

  def isEmpty: Boolean

  def insert(e: E): Heap[E]

  def merge(h: Heap[E]): Heap[E] = {
    def makeT(e:E,a:Heap[E],b:Heap[E]):Heap[E] = if (a.rank >= b.rank) Node(e,a,b,b.rank+1) else Node(e,b,a,a.rank+1)
    (this,h) match {
      case (Nil(),_) => h
      case (_,Nil()) => this
      case (Node(x,l1,r1,_),Node(y,l2,r2,_)) => if (x < y)  makeT(x,l1,r1.merge(h)) else makeT(y,l2,this.merge(r2))
    }
  }

  def findMin: E

  def deleteMin: Heap[E]

  protected def rank:Int
}


object Heap {

  private val emptyEl = new Nil[Nothing]

  def empty[E] = emptyEl.asInstanceOf[Heap[E]]

}

private case class Node[E](e: E, left: Heap[E], right: Heap[E], rank: Int)(implicit  ordering:Ordering[E]) extends Heap[E]()(ordering) {

  def deleteMin = left.merge(right)

  val findMin = e

  def insert(e: E):Heap[E] = Node(e,empty,empty,1).merge(this)

  def isEmpty = false

}

private case class Nil[E]()(implicit ordering:Ordering[E]) extends Heap[E]()(ordering) {

  def deleteMin = throw new NoSuchElementException

  def findMin = throw new NoSuchElementException

  def insert(e: E):Heap[E] = Node[E](e,Heap.empty,Heap.empty,1)

  def isEmpty = true

  protected def rank = 0
}

object PG {

  def main(args: Array[String]) {
    val e:Heap[Int] = Heap.empty[Int]
    val e1:Heap[Int] = e insert 3
    val e2:Heap[Int] = e1 insert 5
    val e3:Heap[Int] = e2.deleteMin
    println()
  }
}

此操作失败,并显示以下错误:

Exception in thread "main" java.lang.ClassCastException: java.lang.Integer cannot be cast to scala.math.Ordered
    at scala.math.LowPriorityOrderingImplicits$$anon$3.compare(Ordering.scala:117)
    at scala.math.Ordering$class.lt(Ordering.scala:71)
    at scala.math.LowPriorityOrderingImplicits$$anon$3.lt(Ordering.scala:117)
    at scala.math.Ordering$Ops.$less(Ordering.scala:100)
    at my.collections.Heap.merge(Heap.scala:27)
    at my.collections.Node.insert(Heap.scala:53)
    at my.collections.PG$.main(Heap.scala:77)
    at my.collections.PG.main(Heap.scala)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25)
    at java.lang.reflect.Method.invoke(Method.java:597)
    at com.intellij.rt.execution.application.AppMain.main(AppMain.java:115)

我的问题是:

  1. 我究竟做错了什么,我该如何解决?
  2. 是否有系统的方法来理解这些错误?

3 个答案:

答案 0 :(得分:2)

由于你得到一个类强制转换异常,我会在你的代码中查看可能的错误强制转换。我可以找到一个演员:

def empty[E] = emptyEl.asInstanceOf[Heap[E]]

由于E不是协变,这是投射错误,Heap[Nothing]不是Heap[E]的子类!

你需要做一些工作才能在这里做E协变,所以除非你需要这个功能,否则你可以修复演员:

object Heap {
    def empty[E](implicit  ordering:Ordering[E]) = new Nil[E]
}

顺便说一句,如果Heap中的E是协变的(例如Heap[+E]),则您不需要进行投射,因为scalac会接受您返回{{1 {}为Nil[Nothing]。因此,除非你确切知道为什么使用Heap[E]并且没有办法解决它,否则几乎肯定是错误的。

答案 1 :(得分:1)

好的,这里有更多证据证明我的答案是正确的。

class A[B](implicit ord: Ordering[B]) {
  def compare(x: B, y: B) = ord.lt(x, y)
}
object A {
  private val e = new A[Nothing] ()
  def empty[X] = e.asInstanceOf[A[X]]
}
val test = A.empty[Int] // works
test.compare(1, 2)      // ouch

你可以看到,对于类型参数进行错误的演员是完全有效的!这是悲伤的JVM故事类型擦除的一部分 - 因为在运行时进行转换,A[B]A[Nothing]会减少为A [java.lang.Object],因此施展本身并不是禁止的。

事实(错误)只是在稍后才揭示......

答案 2 :(得分:0)

这是我见过的最糟糕的例子之一,当你骗到编译器时会出现什么问题! : - )

我会逐行显示出来的内容,因此可以看到正在发生的事情(但是0__是正确的,值得接受的答案)。

val e:Heap[Int] = Heap.empty[Int]

那叫

def empty[E] = emptyEl.asInstanceOf[Heap[E]]

哪个电话

private val emptyEl = new Nil[Nothing]

采用隐式Ordering[Nothing]。我很惊讶有这样的事情,所以我查了一下。有关Ordering的一件事是,如果您的收藏集是Ordered,则会将Ordering提供给它。提供此方法的方法是:

implicit def ordered [A <: Ordered[A]]: Ordering[A] = new Ordering[A] {
    def compare(x: A, y: A) = x.compare(y)
}

所以,这是关于Nothing的交易:它是一切的子类。因此,它是Ordered[Nothing]的子类,因此Ordering[Nothing]可用。

无论如何,到目前为止还没有错误。下一行是:

val e1:Heap[Int] = e insert 3

这会在insert上调用Nil

def insert(e: E):Heap[E] = Node[E](e,Heap.empty,Heap.empty,1)

请注意,没有Ordering[E]传递给方法insert,因此它使用传递给NilOrdering[Nothing]的方法。但是,仍然没有错误,所以下一行:

 val e2:Heap[Int] = e1 insert 5

这会在insert上调用Node

def insert(e: E):Heap[E] = Node(e,empty,empty,1).merge(this)

同样,没有传递Ordering[E],所以它使用了它在创建时收到的那个,仍然是Ordering[Nothing]。这将最终导致错误,在merge

这一行
  case (Node(x,l1,r1,_),Node(y,l2,r2,_)) => if (x < y)  makeT(x,l1,r1.merge(h)) else makeT(y,l2,this.merge(r2))

表达式x < y是问题所在。 <上没有定义x方法,因为x只是一个通用的E,所以它会通过隐式转换来执行它:

new ordering.Ops(x) < y

Ops.<的位置:

def <(rhs: T) = lt(lhs, rhs)

lt定义ordering(已导入)。换句话说,它执行:

ordering.lt(x, y)

这将导致对ordering.compare的调用。我们之前看到了Ordering[Nothing]的定义,即:

x.compare(y)

这是错误发生的地方。 x的类型为java.lang.Integer(因为自动装箱)。方法compare来自scala.math.Orderedjava.lang.Integer显然没有实现。

所以它失败了。所有这一切都是因为有点白色谎言......: - )

另一方面,如果正在使用Ordering[Int],则会使用此定义:

  def compare(x: Int, y: Int) = 
      if (x < y) -1
      else if (x == y) 0
      else 1
  }

此处<存在,因为xInt