可以对符合结果类型的通用值执行模式匹配吗?

时间:2012-01-10 02:57:05

标签: scala types pattern-matching

是否可以执行模式匹配,其结果符合外部方法的类型参数?例如。给出:

trait Key[A] {
  def id: Int
  def unapply(k: Key[_]): Boolean = k.id == id // used for Fail2
  def apply(thunk: => A): A = thunk // used for Fail3
}

trait Ev[A] {
  def pull[A1 <: A](key: Key[A1]): Option[A1]
}

trait Test extends Ev[AnyRef] {
  val key1 = new Key[String] { def id = 1 }
  val key2 = new Key[Symbol] { def id = 2 }
}

是否有Test(其pull方法)的实现,它使用key参数上的模式匹配,并为每个选中的密钥返回Option[A1],而不使用是asInstanceOf

有些可怜的尝试:

class Fails1 extends Test {
  def pull[A1 <: AnyRef](key: Key[A1]): Option[A1] = key match {
    case `key1` => Some("hallo")
    case `key2` => Some('welt)
  }
}

class Fails2 extends Test {
  def pull[A1 <: AnyRef](key: Key[A1]): Option[A1] = key match {
    case key1() => Some("hallo")
    case key2() => Some('welt)
  }
}

class Fails3 extends Test {
  def pull[A1 <: AnyRef](key: Key[A1]): Option[A1] = key match {
    case k @ key1() => Some(k("hallo"))
    case k @ key2() => Some(k('welt))
  }
}

没有用,显然......唯一的解决方案是施放:

class Ugly extends Test {
  def pull[A1 <: AnyRef](key: Key[A1]): Option[A1] = key match {
    case `key1` => Some("hallo".asInstanceOf[A1])
    case `key2` => Some('welt  .asInstanceOf[A1])
  }
}

val u = new Ugly
u.pull(u.key1)
u.pull(u.key2)

2 个答案:

答案 0 :(得分:1)

问题确实是模式匹配会忽略所有已擦除的类型。然而,人们可以使用一些隐含的诡计。以下内容将保留返回类型匹配所提供的类型解析。

abstract class UnErased[A]
implicit case object UnErasedString extends UnErased[String]
implicit case object UnErasedSymbol extends UnErased[Symbol]

class UnErasedTest extends Test {
  def pull[ A1 <: AnyRef ]( key: Key[ A1 ])(implicit unErased: UnErased[A1]): Option[ A1 ] = unErased match {
    case UnErasedString if key1.id == key.id => Some( "hallo" )
    case UnErasedSymbol if key2.id == key.id => Some( 'welt )
    case _ => None
  }
}

val u = new UnErasedTest 
println( u.pull( u.key1 ) )
println( u.pull( u.key2 ) )

然而,这几乎等同于仅定义Key的单独子类。 我发现以下方法更可取但是如果现有代码使用Key [String]而无法更改为必要的KeyString(或者需要更改的工作量太多),它可能无效。

trait KeyString extends Key[String]
trait KeySymbol extends Key[Symbol]

trait Test extends Ev[ AnyRef ] {
   val key1 = new KeyString { def id = 1 }
   val key2 = new KeySymbol { def id = 2 }
}

class SubTest extends Test {
  def pull[ A1 <: AnyRef ]( key: Key[ A1 ]): Option[ A1 ] = key match {
    case k: KeyString if key1.id == k.id => Some( "hallo" )
    case k: KeySymbol if key2.id == k.id => Some( 'welt )
    case _ => None
  }
}

val s = new SubTest
println( s.pull( s.key1 ) )
println( s.pull( s.key2 ) )

答案 1 :(得分:0)

我在这里提供了一个基于Neil Essy答案的封闭类型方法的扩展示例(显示了我的更多上下文):

trait KeyLike { def id: Int }

trait DispatchCompanion {
  private var cnt = 0
  sealed trait Value
  sealed trait Key[V <: Value] extends KeyLike {
    val id = cnt  // automatic incremental ids
    cnt += 1
  }
}

trait Event[V] {
  def apply(): Option[V] // simple imperative invocation for testing
}

class EventImpl[D <: DispatchCompanion, V <: D#Value](
  disp: Dispatch[D], key: D#Key[V]) extends Event[V] {

  def apply(): Option[V] = disp.pull(key)
}

trait Dispatch[D <: DispatchCompanion] {
  // factory method for events
  protected def event[V <: D#Value](key: D#Key[V]): Event[V] =
    new EventImpl[D, V](this, key)

  def pull[V <: D#Value](key: D#Key[V]): Option[V]
}

然后,以下场景编译时没有太多杂乱:

object Test extends DispatchCompanion {
  case class Renamed(before: String, now: String) extends Value
  case class Moved  (before: Int   , now: Int   ) extends Value
  private case object renamedKey extends Key[Renamed]
  private case object movedKey   extends Key[Moved  ]
}
class Test extends Dispatch[Test.type] {
  import Test._

  val renamed = event(renamedKey)
  val moved   = event(movedKey  )

  // some dummy propagation for testing
  protected def pullRenamed: (String, String) = ("doesn't", "matter")
  protected def pullMoved  : (Int   , Int   ) = (3, 4)

  def pull[V <: Value](key: Key[V]): Option[V] = key match {
    case _: renamedKey.type => val p = pullRenamed; Some(Renamed(p._1, p._2))
    case _: movedKey.type   => val p = pullMoved;   Some(Moved(  p._1, p._2))
  }
}

...并产生预期的结果:

val t = new Test
t.renamed()
t.moved()

现在我唯一没有得到的,我发现丑陋的是我的案件必须是

的形式
case _: keyCaseObject.type =>

并且不能

case keyCaseObject =>

我非常喜欢。这种限制来自哪些想法?