Search code examples
scalaimplicittype-level-computationpath-dependent-type

Force dependent types resolution for implicit calls


I have a wrapper trait :


trait Wrapper[T] {

  ...
  type Own[F[_]] <: OwnThing[F]
  def ask[F[_]](implicit own: Own[F])

}

And different implementations, here is an example :

class CombinedWrapper[A, B](wrapperA: Wrapper[A], wrapperB: Wrapper[B]) extends Wrapper[(A, B)] {
  override type Own[F[_]] = SomeOwnThing[(A, B), wrapperA.Own[F], wrapperB.Own[F]]
  override def ask[F[_]](implicit own: Own[F]) = ???
}

But the implicit resolution will not resolve the path-dependent types, therefore not finding any implicits.

Is there a way to force the resolution of path-dependent types? As for now, I just think of them as variables but at the type level.

Edit : A more complete version:


trait Wrapper[T] {

  ...
  type Own[F[_]] <: OwnThing[F, T]
  def ask[F[_]](implicit own: Own[F])

}

sealed trait OwnThing[F[_], A]

trait SomeOwnThing[F[_], A, B, ThingA <: OwnThing[F, A], ThingB <: OwnThing[F, B]] extends OwnThing[F, (A, B)] {
  def underlyingA: ThingA
  def underlyingB: ThingB
}

class CombinedWrapper[A, B](wrapperA: Wrapper[A], wrapperB: Wrapper[B]) extends Wrapper[(A, B)] {
  override type Own[F[_]] = SomeOwnThing[F, A, B, wrapperA.Own[F], wrapperB.Own[F]]
  override def ask[F[_]](implicit own: Own[F]) = ???
}

Edit2: A non-working example

trait SimpleOwn[F[_], A] extends OwnThing[F, A]

class SimpleWrapper[T] extends Wrapper[T] {
    override type Own[F[_]] = SimpleOwn[F, T]
    override def ask[F[_]](implicit own: Own[F]) = ???
  }

val combined = new CombinedWrapper[String, Int](new SimpleWrapper[String], new SimpleWrapper[Int])
type Id[A] = A

//Simple case for base types
implicit val intOwn: SimpleOwn[Id, Int] = new SimpleOwn[Id, Int]
implicit val stringOwn: SimpleOwn[Id, String] = new SimpleOwn[Id, String]

//Should combine the two above
implicit def composeOwnIds[A, B, ThingA <: OwnThing[Id, A], ThingB <: OwnThing[Id, B]](implicit aOwn: ThingA, bOwn: ThingB): SomeOwnThing[Id, A, B, ThingA, ThingB] = new SomeOwnThing {
  override def underlyingA: ThingA = aOwn
  override def underlyingB: ThingB = bOwn
}

//Should work but cant find implicit
combined.ask[Id]

Edit3: For me, the root of the problem is in CombinedWrapper at the type member definition. I think Scala does not resolve the path-dependent-types use in the definition.

I can say this because

new SimpleWrapper[String].ask

does compile


Solution

  • Firstly fix couple of typos

    //class CombinedWrapper[A, B](wrapperA: Wrapper[A], wrapperB: Wrapper[B]) extends Wrapper[(A, B)] {
      class CombinedWrapper[A, B](val wrapperA: Wrapper[A], val wrapperB: Wrapper[B]) extends Wrapper[(A, B)] { ...
    
    //implicit val intOwn: SimpleOwn[Id, Int] = new SimpleOwn[Id, Int]
      implicit val intOwn: SimpleOwn[Id, Int] = new SimpleOwn[Id, Int] {}
    //implicit val stringOwn: SimpleOwn[Id, String] = new SimpleOwn[Id, String]
      implicit val stringOwn: SimpleOwn[Id, String] = new SimpleOwn[Id, String] {}
    
    //implicit def composeOwnIds[A, B, ThingA <: OwnThing[Id, A], ThingB <: OwnThing[Id, B]](implicit aOwn: ThingA, bOwn: ThingB): SomeOwnThing[Id, A, B, ThingA, ThingB] = new SomeOwnThing {
      implicit def composeOwnIds[A, B, ThingA <: OwnThing[Id, A], ThingB <: OwnThing[Id, B]](implicit aOwn: ThingA, bOwn: ThingB): SomeOwnThing[Id, A, B, ThingA, ThingB] = new SomeOwnThing[Id, A, B, ThingA, ThingB] { ...
    

    Then standard way to debug implicits is to resolve them manually (maybe specifying some type parameters explicitly) and see compile errors.

    new SimpleWrapper[String].ask
    new SimpleWrapper[Int].ask
    

    are actually

    new SimpleWrapper[String].ask[Id](stringOwn)
    new SimpleWrapper[Int].ask[Id](intOwn)
    

    If you try

    combined.ask[Id](composeOwnIds(stringOwn, intOwn))
    

    you'll have

    Error: type mismatch;
     found   : App.intOwn.type (with underlying type App.SimpleOwn[App.Id,Int])
     required: App.combined.wrapperB.Own[App.Id]
    Error: type mismatch;
     found   : App.stringOwn.type (with underlying type App.SimpleOwn[App.Id,String])
     required: App.combined.wrapperA.Own[App.Id]
    

    If you try

    combined.ask[Id](composeOwnIds[String, Int, SimpleOwn[Id, String], SimpleOwn[Id, Int]](stringOwn, intOwn))
    

    you'll have

    Error: type mismatch;
     found   : App.SomeOwnThing[App.Id,String,Int,App.SimpleOwn[App.Id,String],App.SimpleOwn[App.Id,Int]]
     required: App.combined.Own[App.Id]
        (which expands to)  App.SomeOwnThing[App.Id,String,Int,App.combined.wrapperA.Own[App.Id],App.combined.wrapperB.Own[App.Id]]
    

    If you replace

    val combined = new CombinedWrapper[String, Int](new SimpleWrapper[String], new SimpleWrapper[Int])
    

    with

    val strWrapper = new SimpleWrapper[String]
    val intWrapper = new SimpleWrapper[Int]
    val combined = new CombinedWrapper[String, Int](strWrapper, intWrapper)
    

    then

    combined.ask[Id](composeOwnIds[String, Int, strWrapper.Own[Id], intWrapper.Own[Id]](stringOwn, intOwn))
    

    will give

    Error: type mismatch;
     found   : App.SomeOwnThing[App.Id,String,Int,App.strWrapper.Own[App.Id],App.intWrapper.Own[App.Id]]
        (which expands to)  App.SomeOwnThing[App.Id,String,Int,App.SimpleOwn[App.Id,String],App.SimpleOwn[App.Id,Int]]
     required: App.combined.Own[App.Id]
        (which expands to)  App.SomeOwnThing[App.Id,String,Int,App.combined.wrapperA.Own[App.Id],App.combined.wrapperB.Own[App.Id]]
      combined.ask[Id](composeOwnIds[String, Int, strWrapper.Own[Id], intWrapper.Own[Id]](stringOwn, intOwn))
    

    The thing is that although combined.wrapperA is strWrapper and combined.wrapperB is intWrapper but types combined.wrapperA.Own[Id] and strWrapper.Own[Id] are different and types combined.wrapperB.Own[Id] and intWrapper.Own[Id] are different too.

    For example if you have

    trait MyTrait { type T }
    val mt = new MyTrait {}
    val mt1 = mt
    

    then values are equal but types are different

    //  implicitly[mt.T =:= mt1.T] // doesn't compile
    //  implicitly[mt1.T =:= mt.T] // doesn't compile
    

    Try to modify CombinedWrapper adding more type parameters and specify them with specific dependent types at call site

      trait Wrapper[T] {
        type Own[F[_]] <: OwnThing[F, T]
        def ask[F[_]](implicit own: Own[F])
      }
    
      sealed trait OwnThing[F[_], A]
    
      trait SomeOwnThing[F[_], A, B, ThingA <: OwnThing[F, A], ThingB <: OwnThing[F, B]] extends OwnThing[F, (A, B)] {
        def underlyingA: ThingA
        def underlyingB: ThingB
      }
    
    //  class CombinedWrapper[A, B](val wrapperA: Wrapper[A], val wrapperB: Wrapper[B]) extends Wrapper[(A, B)] {
    //    override type Own[F[_]] = SomeOwnThing[F, A, B, wrapperA.Own[F], wrapperB.Own[F]]
    //    override def ask[F[_]](implicit own: Own[F]) = ???
    //  }
    
      class CombinedWrapper[A, B, OwnA[F[_]] <: OwnThing[F, A], OwnB[F[_]] <: OwnThing[F, B]](
        val wrapperA: Wrapper[A] { type Own[F[_]] = OwnA[F] }, 
        val wrapperB: Wrapper[B] { type Own[F[_]] = OwnB[F] }
      ) extends Wrapper[(A, B)] {
        override type Own[F[_]] = SomeOwnThing[F, A, B, OwnA[F], OwnB[F]]
        override def ask[F[_]](implicit own: Own[F]) = ???
      }
    
      trait SimpleOwn[F[_], A] extends OwnThing[F, A]
    
      class SimpleWrapper[T] extends Wrapper[T] {
        override type Own[F[_]] = SimpleOwn[F, T]
        override def ask[F[_]](implicit own: Own[F]) = ???
      }
    
    //  val combined = new CombinedWrapper[String, Int](new SimpleWrapper[String], new SimpleWrapper[Int])
      val strWrapper = new SimpleWrapper[String]
      val intWrapper = new SimpleWrapper[Int]
      val combined = new CombinedWrapper[String, Int, strWrapper.Own, intWrapper.Own](strWrapper, intWrapper)
    
      type Id[A] = A
    
      implicit val intOwn: SimpleOwn[Id, Int] = new SimpleOwn[Id, Int] {}
      implicit val stringOwn: SimpleOwn[Id, String] = new SimpleOwn[Id, String] {}
    
      implicit def composeOwnIds[A, B, ThingA <: OwnThing[Id, A], ThingB <: OwnThing[Id, B]](implicit aOwn: ThingA, bOwn: ThingB): SomeOwnThing[Id, A, B, ThingA, ThingB] = new SomeOwnThing[Id, A, B, ThingA, ThingB] {
        override def underlyingA: ThingA = aOwn
        override def underlyingB: ThingB = bOwn
      }
    
      combined.ask[Id] // compiles
    
      new SimpleWrapper[String].ask  // compiles
    

    Using Aux type

      trait Wrapper[T] {
        type Own[F[_]] <: OwnThing[F, T]
        def ask[F[_]](implicit own: Own[F])
      }
    
      object Wrapper {
        type Aux[T, Own0[F[_]] <: OwnThing[F, T]] = Wrapper[T] { type Own[F[_]] = Own0[F] }
      }
    
      class CombinedWrapper[A, B, OwnA[F[_]] <: OwnThing[F, A], OwnB[F[_]] <: OwnThing[F, B]](
        val wrapperA: Wrapper.Aux[A, OwnA], val wrapperB: Wrapper.Aux[B, OwnB]
      ) extends Wrapper[(A, B)] {
        override type Own[F[_]] = SomeOwnThing[F, A, B, OwnA[F], OwnB[F]]
        override def ask[F[_]](implicit own: Own[F]) = ???
      }