score:0

Accepted answer

Thanks to type erasure, after fiddling around with this for hours, this is the only viable solution:

trait StringOuter[S] extends Outer[S] { type A1 = String }

Trying anything else is a waste of energy. Just stick with flat classes and invariant types. Forget about the scenario of covariant types and pattern matching.

score:0

If we forget about the pattern matching for a moment, one can use plain old reflection to some extent:

import reflect.ClassTag

trait Outer[S] {
  type A1
  def inner: Inner[S] { type A = A1 }
  def as[A](implicit tag: ClassTag[A]): Option[Outer[S] { type A1 = A }] =
    inner.peer match {
      case _: A => Some(this.asInstanceOf[Outer[S] { type A1 = A }])
      case _ => None
    }
}

trait Inner[S] {
  type A
  def peer: A
}

Test:

trait Foo[S]    

val x = new Outer[Unit] { 
  type A1 = String
  val inner = new Inner[Unit] {
    type A = String
    val peer = "foo"
  }
}

val y = new Outer[Unit] { 
  type A1 = Foo[Unit]
  val inner = new Inner[Unit] {
    type A = Foo[Unit]
    val peer = new Foo[Unit] {}
  }
}

val xs = x.as[String]
val xi = x.as[Foo[Unit]]

val ys = y.as[String]
val yi = y.as[Foo[Unit]]

The only problem now is that higher-kinded types are not checked:

y.as[Foo[Nothing]]   // Some!

The other idea is to change my design to require the S parameter to be always present. Then

trait Sys[S <: Sys[S]]

trait Inner[S <: Sys[S], +Elem[~ <: Sys[~]]] {
  def peer: Elem[S]
  def as[A[~ <: Sys[~]]](implicit tag: ClassTag[A[S]]): Option[Inner[S, A]] =
    if (tag.unapply(peer).isDefined)
      Some(this.asInstanceOf[Inner[S, A]]) 
    else 
      None
}

type In[S <: Sys[S]] = Inner[S, Any]

trait Foo[S <: Sys[S]] { def baz = 1234 }
trait Bar[S <: Sys[S]]

trait I extends Sys[I]

val i: In[I] = new Inner[I, Foo] { val peer = new Foo[I] {} }
val j: In[I] = new Inner[I, Bar] { val peer = new Bar[I] {} }

assert(i.as[Foo].isDefined)
assert(i.as[Bar].isEmpty  )
assert(j.as[Foo].isEmpty  )
assert(j.as[Bar].isDefined)

Or the last version changed back to using a type member:

trait Inner[S <: Sys[S]] {
  type Elem

  def peer: Elem
  def as[A[~ <: Sys[~]]](implicit tag: ClassTag[A[S]]): Option[InnerT[S, A]] =
    if (tag.unapply(peer).isDefined) 
      Some(this.asInstanceOf[InnerT[S, A]])
    else
      None
}

type InnerT[S <: Sys[S], A[~ <: Sys[~]]] = Inner[S] { type Elem = A[S] }


val i: Inner[I] = new Inner[I] { type Elem = Foo[I]; val peer = new Foo[I] {} }
val j: Inner[I] = new Inner[I] { type Elem = Bar[I]; val peer = new Bar[I] {} }

assert(i.as[Foo].isDefined)
assert(i.as[Bar].isEmpty  )
assert(j.as[Foo].isEmpty  )
assert(j.as[Bar].isDefined)

val ix: InnerT[I, Foo] = i.as[Foo].get
ix.peer.baz

...that may be advantageous for implicit resolution such as Serializer[Inner[S]] which can be easily broken when variant type parameters are involved.


Related Query

More Query from same tag