scalaabstract-classabstract-typebounded-types

Scala: access "static" member of bounded generic type


I want to achieve the following:

abstract class Super {
  def typeSpecific: Int
}

class SubA extends Super {
  def typeSpecific = 1
}
class SubB extends Super {
  def typeSpecific = 2
}

class Tester[T <: Super] {
  def test = T.typeSpecific
}

val testerA = new Tester[SubA]
val testerB = new Tester[SubB]

testerA.test // should return 1
testerB.test // should return 2

Is something like this possible in Scala? This fails because the value of T is not found in Tester.test.


Solution

  • typeSpecific is not a static member, it belongs to instances of SubA and SubB, which you don't have. You also can't statically access anything from a type parameter (it's a type, not an object).

    This won't work as is, because you don't have instances of SubA and SubB, nor can you obtain them via new Tester[SubA]. But you can require that Tester mixes in a type of Super in order to make it one (and thus have typeSpecific). This would require you change Super, SubA, and SubB to traits, and would also make your instance anonymous classes.

    trait Super {
      def typeSpecific: Int
    }
    
    trait SubA extends Super {
      def typeSpecific = 1
    }
    trait SubB extends Super {
      def typeSpecific = 2
    }
    
    // The self-type `this: A =>` requires the mix-in.
    class Tester[A <: Super] { this: A =>
      def test = typeSpecific
    }
    
    val testerA = new Tester[SubA] with SubA
    val testerB = new Tester[SubB] with SubB
    
    scala> testerA.test
    res2: Int = 1
    
    scala> testerB.test
    res3: Int = 2
    

    You could also require A <: Super as a constructor parameter for Tester, which is probably the cleaner option.

    abstract class Super {
      def typeSpecific: Int
    }
    
    class SubA extends Super {
      def typeSpecific = 1
    }
    class SubB extends Super {
      def typeSpecific = 2
    }
    
    class Tester[A <: Super](s: A) {
      def test = s.typeSpecific
    }
    
    val testerA = new Tester(new SubA)
    val testerB = new Tester(new SubB)
    
    scala> testerA.test
    res5: Int = 1
    
    scala> testerB.test
    res6: Int = 2
    

    Any way you cut it, you're going to need an instance of SubA or SubB.