通过匹配与类型成员对应的成员值来获取精炼类型

时间:2014-04-25 12:24:21

标签: scala pattern-matching type-members

我有一个异构类型的容器

trait Elem

trait Obj {
  type E <: Elem
  def elem: E
}

trait Foo extends Elem

不应该有Obj的任何子类。我正在寻找建议,如何优雅地将Obj的实例与多种元素类型进行匹配,以获得精确的Obj

例如:

def withFooObj(obj: Obj { type E = Foo }) = ()

def test(obj: Obj): Unit = obj.elem match {
  case f: Foo =>
    withFooObj(obj.asInstanceOf[Obj { type E = Foo }]) // !
  case _ =>
}

摆脱演员会有什么优雅的想法?理想情况下,它可以作为模式匹配。


例如,我可以创建单独的提取器:

object IsFoo {
  def unapply(obj: Obj): Option[Obj { type E = Foo }] =
    if (obj.elem.isInstanceOf[Foo]) Some(obj.asInstanceOf[Obj { type E = Foo }])
    else None
}

def test(obj: Obj): Unit = obj match {
  case IsFoo(x) => withFooObj(x)
  case _ =>
}

但是我有很多Elem的子类型,并且更喜欢使用通用提取器。


编辑:由于这似乎很难,我想添加另一个放宽:允许更改Obj以包含类型参数而不是类型成员(如果它有助于)。 允许Obj

的子类

3 个答案:

答案 0 :(得分:1)

我希望这是不可能的。考虑(假设class Foo extends Elem

val obj1 = new Obj {
  type E = Elem
  def elem = new Foo
}

由于obj.elemf: Foo匹配,但obj1实际上没有Obj { type E = Foo }类型,因此您必须进行投射。 在这种情况下,此演员实际上是安全的,但仅仅因为Obj没有例如任何 E的方法。实际上,这是不安全的,因为第一次调用elem的值有类Foo并不意味着elem的所有电话都会返回Foo

编辑:如果您只是将演员隐藏在提取器中,那么您可以做以下事情:

case class IsElem[T <: Elem]()(implicit ct: ClassTag[T]) {
  def unapply(obj: Obj): Option[Obj { type E = T }] =
    if (ct.runtimeClass.isInstance(obj.elem)) Some(obj.asInstanceOf[Obj { type E = T }])
      else None
}

val IsFoo = IsElem[Foo]
val IsBar = IsElem[Bar]
...

EDIT2:使用类型参数,你可以

(obj, obj.elem) match {
  case (obj: Obj[Foo] @unchecked, f: Foo) => ...
  case (obj: Obj[Bar] @unchecked, f: Bar) => ...
}

显然,这对安全问题没有帮助。

答案 1 :(得分:0)

(这里我想要聪明:)

trait Idea {
  def resolve[A](obj: Obj)
      (f: Function1[(Obj { type E = E1 }, E1) forSome { type E1 <: Elem }, A]): A
}

def test(obj: Obj, idea: Idea): Unit = idea.resolve(obj) {
  case (x, _: Foo) => withFooObj(x)
  case _ =>
}

但Scalac并不认为E1 == Foo在这里......

答案 2 :(得分:0)

我将专注于您对异构容器的需求,并忽略某些细节,如类型成员和模式匹配。

<强>摘要

目标是根据Obj类型处理elem。工作解决方案的重要部分:

  • 使Obj一个抽象类,其类型参数代表elem
  • 使用隐式参数自动关联与Processor相同类型的参数化elem
  • 为您要使用的所有Processor类型定义隐式Elem

我考虑将隐式Processor定义为最小样板(如果你实际上可以称之为样板文件)。你需要定义elem - 特定的处理器,所以这些隐式对象是组织它们的好地方。

代码:

trait Elem {
  // for debugging only
  def typeName: String
}

abstract class Obj[E <: Elem](implicit val processor: Processor[E]) {
  def elem: E

  def process(): Unit = processor.process(this)
}

trait Processor[R <: Elem] {
  def process(obj: Obj[R]): Unit
}
object Processor {
  implicit val fooProcessor = new Processor[Foo] {
    def process(obj: Obj[Foo]): Unit = println(s"Processing Foo: ${obj.elem.typeName}")
  }
  implicit val barProcessor = new Processor[Bar] {
    def process(obj: Obj[Bar]): Unit = println(s"Processing Bar: ${obj.elem.typeName}")
  }
  implicit val widgetProcessor = new Processor[Widget] {
    def process(obj: Obj[Widget]): Unit = println(s"Processing Widget: ${obj.elem.typeName}")
  }
}

trait Foo extends Elem { def typeName = "Foo" }
trait Bar extends Elem { def typeName = "Bar" }
trait Widget extends Elem { def typeName = "Widget" }

def test2[R <: Elem](obj: Obj[R]): Unit = obj.process()

// MAIN

val objFoo = new Obj[Foo] {
  def elem: Foo = new Foo { }
}
println("===== test2 on a static-typed Foo")
test2(objFoo)

val objBar = new Obj[Bar] {
  def elem: Bar = new Bar { }
}
println("===== test2 on a static-typed Bar")
test2(objBar)

val objWidget = new Obj[Widget] {
  def elem: Widget = new Widget { }
}
println("===== test2 on a static-typed Widget")
test2(objWidget)

println("===== test2 on a heterogeneous list of `Obj`s")
val heteroList = List(objFoo, objBar, objWidget)
heteroList.foreach(test2(_))

输出:

===== test2 on a static-typed Foo
Processing Foo: Foo
===== test2 on a static-typed Bar
Processing Bar: Bar
===== test2 on a static-typed Widget
Processing Widget: Widget
===== test2 on a heterogeneous list of `Obj`s
Processing Foo: Foo
Processing Bar: Bar
Processing Widget: Widget