【问题标题】:Possible to perform pattern match on a generic value with type conforming result?可以对具有类型符合结果的泛型值执行模式匹配吗?
【发布时间】:2012-02-06 13:19:46
【问题描述】:

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

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)

【问题讨论】:

标签: scala types pattern-matching


【解决方案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 子类。 我发现以下方法更可取,但如果现有代码使用您无法更改为必要的 KeyString(或更改工作量太大)的 Key[String],它可能无法正常工作。

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 ) )

【讨论】:

  • 非常感谢尼尔。我之前使用方法 (key[A]()) 生成了我的密钥,您的方法向我表明,我可以通过需要实际的封闭类型来解决问题。我在单独的答案中添加了基于此的方法。我喜欢您的第一个示例中的匹配,因为它使用案例对象。我无法在我的扩展示例中使用它,我不知道为什么——也许你有一个想法?
【解决方案2】:

我在这里提供了一个基于 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 =>

我非常喜欢。有什么想法是从哪里来的?

【讨论】:

    猜你喜欢
    • 2016-10-29
    • 2011-10-25
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2018-06-20
    • 2013-04-10
    • 1970-01-01
    相关资源
    最近更新 更多