質問

I am revisiting a problem of generic wrappers for grouping heterogeneous elementary types together. I am using type members, so now the structure looks like this:

trait Outer[S] {
  type A1
  def inner: Inner[S] { type A = A1 }
}

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

The problem then of course is to test for specific objects, e.g.:

def test[S](o: Outer[S]): Option[Outer[S] { type A1 = String }] = o match {
  case os: Outer[S] { type A1 = String } => Some(os)
  case _ => None
}

This doesn't work because of type erasure. The problem is that I must abstract over type parameter arity for the the peer, that is, there are (most) peers which do have one type parameter [S] as well, but others don't. Therefore, using a type constructor parameter for Inner and/or Outer is not feasible.

The cheap solution is to require actual sub-classes:

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

def test1[S](o: Outer[S]): Option[Outer[S] { type A1 = String }] = o match {
  case os: StringOuter[S] => Some(os)
  case _ => None
}

But I don't like this solution because I will have a lot of different peers, and I don't want to create dedicated wrapper classes for each of them. Also for example copying these objects becomes annoying if I have to write the copying method in each and every of these sub-classes.

So I'm left with class-tags perhaps? How would this be solved if I have the following two peer types with different type parameter arity:

trait Foo[S]
type Inner1[S] = Inner[S] { type A = Foo[S] }
type Inner2[S] = Inner[S] { type A = String }

?

役に立ちましたか?

解決 2

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.

他のヒント

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.

ライセンス: CC-BY-SA帰属
所属していません StackOverflow
scroll top