How to ensure subclass relationship when extending base with more typed parameters


I have the following code:

object Exp1 {

  sealed trait S
  sealed trait S1 extends S
  sealed trait S2 extends S

  abstract class BaseSampler[X <: S,T]
  case class BSampler1[T](x: T) extends BaseSampler[S1,T]
  case class BSampler2[T](x: T) extends BaseSampler[S2,T]

  sealed trait UseSampler[T] {
    def getSampler[L <: S]: BaseSampler[L,T]

  final case class Trouble[T](v: T) extends UseSampler[T] {
    override def getSampler[L <: S]: BSampler1[T] = BSampler1(v)


I am having trouble with UseSampler[T]overriding method. Specifically:

overriding method getSampler in trait UseSampler of type [L <: Exp1.S]=> Exp1.BaseSampler[L,T];
[error]  method getSampler has incompatible type
[error]     override def getSampler[L <: S]: BSampler1[T] = BSampler1(v)
[error]                  ^
[info] [L <: Exp1.S]=> Exp1.BSampler1[T] <: [L <: Exp1.S]=> Exp1.BaseSampler[L,T]?
[info]   => Exp1.BSampler1[T] <: => Exp1.BaseSampler[L,T]?
[info]   false
[info] false 

I am aware that the problem is caused by the “missing” type parameter. If I remove the base class type parameter, compilation is good. I have also tried altering the variance of the base class but cannot get => Exp1.BSampler1[T] <: => Exp1.BaseSampler[L,T].

So I have two questions:

  1. How can I ensure the relationship is true Exp1.BSampler1[T] <: => Exp1.BaseSampler[L,T].
  2. I am aware that once I “remove” (fix) one of the base class type parameters, I loose this information. Should we avoid this or is tis an acceptable pattern of use?


Maybe you want to make parameter X covariant

Thanks for the suggestion but I have already tried that with no success.

Honestly there is probably a use for variance here, but not yet.

More importantly, you have placed the L quantifier in the wrong place. Currently, your getSampler promises "I can give you an appropriate BaseSampler for all S subtypes. But you mean for each UseSampler subclass to support a single, specific S subtype, don’t you?

There are a couple ways to do that right–i.e. specify the functional dependency from S subtypes to BaseSampler (probably higher-kinded) subtypes–but you have to clarify what the intended relationship is between S, BaseSampler, and UseSampler is, first.

If you don’t mean what I said, you should still be able to do what you’re trying to do–i.e. support every S subtype in every UseSampler. But that requires different tactics.

Yes. For a little context, the S is used to tag the type of sampling. This information will then be used to
combine several samplers.

S indicates if a sampler is finite or infinite. The implementations of the base samplers (BaseSampler) include ranges (finite), lists of options (finite) and various distributions (infinite). The UseSampler will “combine” one or more BaseSampler to produce a new BaseSampler (cross product, pair all etc. )

This is the issue. How do I get the L' from for exampleBSampler1’ when it is not explicit?
Note that if I add the L to these implementations of BaseSampler I have to set it elsewhere.
I am wondering if “eliminating” a type parameter as I have is a valid use of the typing system.

Thanks for the feedback.

I think I have solved this. As was previously pointed out:

so we need only use an existentially quantified type parameter. The solution is shown below. Note that I have added the type information to show what I was trying to accomplish. In addition to this I could have set the S types in BSampler1 and BSampler2 directly but the S would then not be visible.

Thanks for the input.

object Exp1 {

  sealed trait S
  sealed trait S1 extends S
  sealed trait S2 extends S

  abstract class BaseSampler[L <: S, T]

  case class BSampler1[L <: S, T](x: T) extends BaseSampler[L, T]
  case class BSampler2[L <: S, T](x: T) extends BaseSampler[L, T]

  sealed trait UseSampler[T] {
    def getSampler: BaseSampler[_, T]

  final case class Option1[T](v: T) extends UseSampler[T] {
    override def getSampler: BSampler1[S1, T] = BSampler1(v)

  final case class Option2[T](v: T) extends UseSampler[T] {
    override def getSampler: BSampler2[S2, T] = BSampler2(v)

  val bs2: BSampler2[S2, Int] = BSampler2(1)
  val bs1: BSampler1[S1, String] = BSampler1("One")

  val t1: Option1[Int] = Option1(10)
  val t2: Option2[String] = Option2("Ten")
  val r1: BSampler1[S1, Int] = t1.getSampler
  val r2: BSampler2[S2, String] = t2.getSampler