繁体   English   中英

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

[英]Possible to perform pattern match on a generic value with type conforming result?

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

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参数上使用模式匹配,并为每个检查的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)

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

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

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

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

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM