Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Generic inheritance in Scala

I'm trying to implement a few structures from Okasaki's book in Scala, and in tests try to keep the actual tests in the base class, only using subclasses to provide the instance-under-test.

For example, a test for unbalanced (tree) set looks as follows:

class UnbalancedSetSpec
  extends SetSpec(new UnbalancedSet[Int])
  with IntElements

where

abstract class SetSpec[E, S](val set: Set[E, S]) extends Specification with ScalaCheck {

  implicit def elements: Arbitrary[E]

  // ...

  private def setFrom(es: Seq[E]): S = es.foldRight(set.empty)(set.insert)
}

Now sometimes I want to specialise the child spec, e.g.

class RedBlackSetSpec
  extends SetSpec(new RedBlackSet[Int])
  with IntElements {

  "fromOrdList" should {
    "be balanced" ! prop { (a: List[Int]) =>
      val s = RedBlackSet.fromOrdList(a.sorted)

      set.isValid(s) should beTrue
    }
  }
}

it fails because there's no method isValid on Set[E, S] — it's defined in RedBlackSet[E]. But if I go ahead and change SetSpec[E, S](val set: Set[E, S]) to SetSpec[E, S, SES <: Set[E, S]](val set: SES), this particular problem disappears, but the code still fails to compile:

Error:(7, 11) inferred type arguments [Nothing,Nothing,okasaki.RedBlackSet[Int]] do not conform to class SetSpec's type parameter bounds [E,S,SES <: okasaki.Set[E,S]]
  extends SetSpec(new RedBlackSet[Int])
          ^

Error:(7, 11) inferred type arguments [Nothing,Nothing,okasaki.UnbalancedSet[Int]] do not conform to class SetSpec's type parameter bounds [E,S,SES <: okasaki.Set[E,S]]
  extends SetSpec(new UnbalancedSet[Int])
          ^

The definition of RedBlackSet is as follows:

package okasaki

class RedBlackSet[E](implicit ord: Ordering[E]) extends Set[E, RBTree[E]] {

so I would expect E to be inferred as Int rather than Nothing, and S as RBTree[Int] — but it doesn't happen.

class RedBlackSetSpec
  extends SetSpec[Int, RedBlackSet.RBTree[Int], RedBlackSet[Int]](new RedBlackSet[Int])
  with IntElements {

and

class UnbalancedSetSpec
  extends SetSpec[Int, BinaryTree[Int], UnbalancedSet[Int]](new UnbalancedSet[Int])
  with IntElements

work fine, but look ugly.

I'm struggling to understand why E and S are not inferred here. Any hints?

like image 490
alf Avatar asked Aug 28 '15 20:08

alf


People also ask

What is generic in Scala?

Most Scala generic classes are collections, such as the immutable List, Queue, Set, Map, or their mutable equivalents, and Stack. Collections are containers of zero or more objects. We also have generic containers that aren't so obvious at first.

Can generic be used with inheritance?

Generics also provide type safety (ensuring that an operation is being performed on the right type of data before executing that operation). Hierarchical classifications are allowed by Inheritance. Superclass is a class that is inherited. The subclass is a class that does inherit.

How do I use generic in Scala?

To use a generic class, put the type in the square brackets in place of A . The instance stack can only take Ints. However, if the type argument had subtypes, those could be passed in: Scala 2.

What is generic type class?

A Generic class simply means that the items or functions in that class can be generalized with the parameter(example T) to specify that we can add any type as a parameter in place of T like Integer, Character, String, Double or any other user-defined type.


1 Answers

This is actually a well-known problem with Scala type inference: it can't infer SES "first" and use it to infer E and S. One solution comes to mind:

class RedBlackSetSpec(override val set: RedBlackSet[Int]) extends SetSpec(set) with IntElements {
  def this() = this(new RedBlackSet[Int])
  ...
}

It becomes less ugly if you make set in SetSpec an abstract val instead of a constructor argument, but with a slight tradeoff in cases you don't need to specialize. I think there should be a better one, but this should work.

like image 89
Alexey Romanov Avatar answered Sep 28 '22 01:09

Alexey Romanov