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

发布于 2024-12-26 00:53:06 字数 1450 浏览 2 评论 0原文

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

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)

Is it possible to perform a pattern match whose result conforms to a type parameter of the outer method? E.g. given:

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

Is there an implementation of Test (its pull method) which uses a pattern match on the key argument and returns Option[A1] for each key checked, without the use of asInstanceOf?

Some pathetic tries:

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

None works, obviously... The only solution is to cast:

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)

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(2

何其悲哀 2025-01-02 00:53:06

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

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

The problem is indeed that pattern matching ignores all erased types. However, there is a little implicit trickery that one could employ. The following will preserve the type resolution provided by the match for the return type.

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

This is however nearly equivalent to just defining separate sub classes of Key. I find the following method preferable however it may not work if existing code is using Key[String] that you can't change to the necessary KeyString (or too much work to change).

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 ) )
还在原地等你 2025-01-02 00:53:06

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

我非常喜欢的形式。有什么想法这个限制来自哪里吗?

I provide here an extended example (that shows more of my context) based on the closed types approach of Neil Essy's answer:

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]
}

Then the following scenario compiles with not too much clutter:

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

...and yields the desired results:

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

Now the only thing I don't get and I find ugly is that my cases must be of the form

case _: keyCaseObject.type =>

and cannot be

case keyCaseObject =>

which I would very much prefer. Any ideas where this limitation comes from?

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文