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.