涉及变量arity类型的运行时类型检查/模式匹配

时间:2014-04-24 08:35:13

标签: scala reflection pattern-matching higher-kinded-types

我正在重新审视通用包装器的问题,以便将异构基本类型组合在一起。我正在使用类型成员,所以现在结构看起来像这样:

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 }

2 个答案:

答案 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 }

尝试其他任何事情都是浪费精力。只需坚持平面类和不变类型。忘掉协变类型和模式匹配的场景。