1
votes

I have an example like this:

abstract class IsBaseTC[A] { type Self }
abstract class JustHoldsTypeMember[A] extends IsBaseTC[A] 
implicit val doubleHoldsTypeMember = new JustHoldsTypeMember[Double] { type Self = Double }

abstract class IsActualTC[A, T](implicit val aIsBaseTc: IsBaseTC[T]) extends IsBaseTC {
  type Self = A
  def get(self: A): aIsBaseTc.Self
}

case class Container[T](
  get: T
)

implicit val containerOfDoubleIsActual = new IsActualTC[Container[Double], Double] {
  def get(self: Self) = self.get // type mismatch; 
                                  // found self.get.type (with underlying type Double) 
                                  // required this.aIsBaseTc.self
}

Which gives me the error shown above. Unless I have failed to follow my own logic through correctly, this.aIsBaseTc.self should resolve to a Double. Is there a way to persuade the compiler that this is the case?

Grateful for any help.

2

2 Answers

1
votes

The thing is in scopes.

Simpler example is

trait A { type T }
implicit val a: A { type T = Int } = null
def test(implicit x: A): Unit = {
  implicitly[x.T =:= Int] // doesn't compile, cannot prove that x.T =:= Int
}

You assume that x is a (aIsBaseTc is doubleHoldsTypeMember in your notations). But actually x is not a, x will be resolved when test is called (in the scope of test call site) but a is defined in current scope (scope of test definition). Similarly, aIsBaseTc is not doubleHoldsTypeMember.

When doing implicit resolution with type parameters, why does val placement matter? (See the difference between implicit x: X and implicitly[X].)

As for any anonymous class

implicit val containerOfDoubleIsActual = new IsActualTC[Container[Double], Double] {
  def get(self: Self) = self.get // type mismatch; 
}

is a shorthand for

class IsActualTCImpl extends IsActualTC[Container[Double], Double] {
  def get(self: Self) = self.get // type mismatch; 
                                 // aIsBaseTc is not doubleHoldsTypeMember here
}
implicit val containerOfDoubleIsActual = 
  new IsActualTCImpl // implicit is resolved here
                     // aIsBaseTc becomes doubleHoldsTypeMember here

And since aIsBaseTc is not doubleHoldsTypeMember, aIsBaseTc.Self is not Double.

Possible fix is to add one more type parameter S to IsActualTC

abstract class IsActualTC[A, T, S](implicit val aIsBaseTc: IsBaseTC[T] {type Self = S}) extends IsBaseTC {
  type Self = A
  def get(self: A): S
}

implicit val containerOfDoubleIsActual = new IsActualTC[Container[Double], Double, Double] {
  def get(self: Self) = self.get
}

or to add a type refinement to the implicit parameter of IsActualTC

abstract class IsActualTC[A, T](implicit val aIsBaseTc: IsBaseTC[T] {type Self = T}) extends IsBaseTC {
  type Self = A
  def get(self: A): aIsBaseTc.Self
}

implicit val containerOfDoubleIsActual = new IsActualTC[Container[Double], Double] {
  def get(self: Self) = self.get
}
1
votes

It is true that both are doubles, but that is the value. It is not the same type.

The type that is requested by def get(self: A) is aIsBaseTc.Self, which is in your case doubleHoldsTypeMember.Self

when trying to access self of doubleHoldsTypeMember you get:

enter image description here

The issue is, that you cannot create this.aIsBaseTc.Self because it is not accessible.

In order to resolve it, you can try to create an instance of Self where you can access it:

abstract class IsBaseTC[A] { type Self; def createSelf(): Self }
abstract class JustHoldsTypeMember[A] extends IsBaseTC[A]
implicit val doubleHoldsTypeMember: JustHoldsTypeMember[Double] = new JustHoldsTypeMember[Double] {
  type Self = Double
  override def createSelf() = 3.14
}

and use it:

implicit val containerOfDoubleIsActual: IsActualTC[Container[Double], Double] = new IsActualTC[Container[Double], Double] {
  override def get(self: Self) = this.aIsBaseTc.createSelf()
  override def createSelf() = ???
}

Then, when running:

containerOfDoubleIsActual.get(Container(4.12))

it will print:

3.14

which was created at createSelf of doubleHoldsTypeMember