Search code examples
scalacovariancepriority-queueabstract-data-type

Co- and contravariant types in generic priority queue


I'm trying to implement in Scala a generic data type parameterized on a type T, which should be Ordered[T]. Specifically, it's a persistent version of Sleator & Tarjan's skew heap priority queues. After adding lots of complicated type parameter declarations based on the explanation here and in Odersky-Spoon-Venners, I'm down to one compiler error before I can test/debug the actual functionality.

Below is a simplified version of my code.

abstract class SkewHeap[+T] {
  // merge two heaps
  def +[U >: T <% Ordered[U]](x : SkewHeap[U]) : SkewHeap[U]
  // remove least element, return new heap
  def delMin[U >: T <% Ordered[U]] : SkewHeap[U]
  def isEmpty : Boolean
  def min : T
  def left  : SkewHeap[T]
  def right : SkewHeap[T]
}

case object Leaf extends SkewHeap[Nothing] {
  def +[U <% Ordered[U]](that : SkewHeap[U]) = that
  def isEmpty = true
}

case class Node[+T](left : SkewHeap[T],
                    min : T,
                    right : SkewHeap[T]) extends SkewHeap[T] {
  def +[U >: T <% Ordered[U]](that : SkewHeap[U]) : SkewHeap[U] =
    that match {
      case Leaf        => this
      case Node(l,y,r) => if (this.min < that.min)
                            Node(this.right + that, this.min, this.left)
                          else
                            Node(this + that.right, that.min, that.left)
    }

  def delMin[U >: T <% Ordered[U]] : SkewHeap[U] = left + right
  def isEmpty = false
}

This gives the following error:

skew.scala:28: error: no implicit argument matching parameter type (T) => Ordered[T] was found.
   def delMin[U >: T <% Ordered[U]] : SkewHeap[U] = left + right

I've tried several variants of the declaration of delMin, but to no avail. I think I understand the problem (method + wants an ordering guarantee), but where should I put this? And is there a way to declare delMin as returning SkewHeap[T] instead of SkewHeap[U]?


Solution

  • abstract class SkewHeap[+T <% Ordered[T]] {
      // merge two heaps
      def +[U >: T <% Ordered[U]](x : SkewHeap[U]) : SkewHeap[U]
      // remove least element, return new heap
      def delMin : SkewHeap[T]
      def isEmpty : Boolean
      def min : T
      def left  : SkewHeap[T]
      def right : SkewHeap[T]
    }
    
    case object Leaf extends SkewHeap[Nothing] {
      def +[U <% Ordered[U]](that : SkewHeap[U]) = that
      def isEmpty = true
      def min = throw new RuntimeException
      def left = throw new RuntimeException
      def right = throw new RuntimeException
      def delMin = throw new RuntimeException
    }
    

    Scala isn't sure how to compare this.min with that.min, becuase it wants to convert this.min to an Ordered[T] and that.min to an Ordered[U]. The simplest answer is to add a type conversion to force this.min to an Ordered[U].

    case class Node[+T <% Ordered[T]](left : SkewHeap[T],
                        min : T,
                        right : SkewHeap[T]) extends SkewHeap[T] {
      def +[U >: T <% Ordered[U]](that : SkewHeap[U]) : SkewHeap[U] =
        that match {
          case Leaf        => this
          case Node(l,y,r) => if ((this.min:Ordered[U]) < that.min)
                                Node(this.right + that, this.min, this.left)
                              else
                                Node(this + that.right, that.min, that.left)
        }
    
      def delMin : SkewHeap[T] = left + right
      def isEmpty = false
    }
    

    But you have a big problem with all of these implicits, and that problem is that you could get a different Ordered implementation in every context where you use the view bound <% Ordered[Something], so you should really look for some other way of making sure your ordering is consistent.