Search code examples
scalatypeclassimplicit

Setting abstract type based on typeclass


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.


Solution

  • 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
    }