Вопрос

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