Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

scala generics and inheritance

Tags:

generics

scala

I have an issue with the following hierarchy in scala:

class ScalaGenericTest {
  def getValue[A, B <: Abstract[A]](clazz: B): A = clazz.a

  def call: String = {
    val sub: Subclass = new Subclass
    getValue(sub)
  }
}

class Subclass extends Abstract[String] {
  def a: String = "STRING"
}

abstract class Abstract[A] {
  def a: A
}

The compiler doesn't seem to be able to bind the generic parameter A in the call to the getValue function -- I think it should be able to infer this from the definition of Subclass. The compile error is as follows:

inferred type arguments [Nothing,Subclass] do not conform to method getValue's type parameter bounds [A,B <: Abstract[A]]

It works if I explicitly pass the generic type arguments to the method, i.e. getValue[String,Subclass](sub) but surely the compiler should be able to infer this?

The same hierarchy works fine in Java:

public class JavaGenericTest {

    public  <T,U extends Abstract<T>> T getValue(U subclass) {
         return subclass.getT();
    }

    public String call(){
        Subclass sub = new Subclass();
        return getValue(sub);
    }

    private static class Subclass extends Abstract<String> {
         String getT(){
            return "STRING";
        }
    }

    private static abstract class Abstract<T> {
        abstract T getT();
    }
}

I'm pretty new to Scala so there's probably some subtlety that I'm missing.

Thanks in advance for any help!

like image 819
paulyb Avatar asked Apr 28 '15 15:04

paulyb


2 Answers

It's a limitation in Scala's type inference. The issue is described in SI-2272 (the example there uses implicits, but the same error occurs when using it explicitly). It's been closed as won't fix.

In that issue, Adriaan Moors advises to avoid constraints that have type variables on both sides. ie. B <: Abstract[A]. An easy work-around would be to avoid the second type parameter altogether.

def getValue[A](clazz: Abstract[A]): A = clazz.a

scala> val sub = new Subclass
sub: Subclass = Subclass@585cbda6

scala> getValue(sub)
res11: String = STRING

Additionally, Adriaan also provided a way of using an implicit <:< as another work around. To put it in the context of your example, it would look like:

def getValue[A, B](b: B)(implicit ev: B <:< Abstract[A]): B = b.a

Where an instance of <:< is provided implicitly through Predef.

like image 183
Michael Zajac Avatar answered Sep 25 '22 20:09

Michael Zajac


I have same problem at one time too. And created large implicit evidence hack to overcome. Afterwards I accidentally look into scala collection api docs and found a solution: http://www.scala-lang.org/api/2.11.4/index.html#scala.collection.generic.GenericTraversableTemplate

class ScalaGenericTest {
  def getValue[A, B[X] <: Abstract[X]](clazz: B[A]): A = clazz.a

  def call: String = {
    val sub: Subclass = new Subclass
    getValue(sub)
  }
}

class Subclass extends Abstract[String] {
  def a: String = "STRING"
}

abstract class Abstract[A] {
  def a: A
}
like image 39
ayvango Avatar answered Sep 24 '22 20:09

ayvango