11

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!

paulyb
  • 168
  • 1
  • 7

3 Answers3

9

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.

Michael Zajac
  • 55,144
  • 7
  • 113
  • 138
  • You can also use an implicit view on the generic parameter with `B <% Abstract[A]`. I think that might be deprecated, but the implicit argument it produces is sufficient evidence for the type inference. – Ben Reich Apr 28 '15 at 16:54
  • Great answer. I did consider the implicit argument approach but couldn't see a good reason why the original didn't work. View bounds using <% have definitely been deprecated. – paulyb Apr 28 '15 at 21:04
3

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
}
ayvango
  • 5,867
  • 3
  • 34
  • 73
1

As addition to Justin's and m-z's answers, another way to make similar declaration keeping two type parameters:

def getValue[A, B](clazz: B)(implicit evidence: B <:< Abstract[A]): A = clazz.a
Odomontois
  • 15,918
  • 2
  • 36
  • 71