我正在重新审视通用包装器的问题,以便将异构基本类型组合在一起。我正在使用类型成员,所以现在结构看起来像这样:
trait Outer[S] {
type A1
def inner: Inner[S] { type A = A1 }
}
trait Inner[S] {
type A
def peer: A
}
当然,问题是测试特定的对象,例如:
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
}
由于类型擦除,这不起作用。问题是我必须为对等体抽象类型参数arity,也就是说,有(大多数)对等体也有一个类型参数[S]
,但是其他对象不是。因此,使用Inner
和/或Outer
的类型构造函数参数是不可行的。
廉价的解决方案是要求实际的子类:
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
}
但我不喜欢这个解决方案,因为我会有很多不同的同行,而且我不想为每个人创建专用的包装类。另外,例如,如果我必须在每个子类中编写复制方法,那么复制这些对象会变得很烦人。
所以我可能会留下类标签吗?如果我有以下两个具有不同类型参数arity的对等类型,将如何解决这个问题:
trait Foo[S]
type Inner1[S] = Inner[S] { type A = Foo[S] }
type Inner2[S] = Inner[S] { type A = String }
答案 0 :(得分:0)
如果我们暂时忘记模式匹配,可以在某种程度上使用普通的旧反射:
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
}
测试:
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]]
现在唯一的问题是没有检查更高级别的类型:
y.as[Foo[Nothing]] // Some!
另一个想法是改变我的设计,要求S
参数始终存在。然后
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)
或者最后一个版本改回使用类型成员:
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
...这对于隐式解决方案可能有利,例如Serializer[Inner[S]]
,当涉及变量类型参数时,它很容易被破坏。
答案 1 :(得分:0)
由于类型擦除,经过几个小时的摆弄,这是唯一可行的解决方案:
trait StringOuter[S] extends Outer[S] { type A1 = String }
尝试其他任何事情都是浪费精力。只需坚持平面类和不变类型。忘掉协变类型和模式匹配的场景。