【问题标题】:Typesafe filteration of List of ADTs by a type parameter通过类型参数对 ADT 列表进行类型安全过滤
【发布时间】:2021-06-30 12:19:27
【问题描述】:

考虑以下示例:

我。

class A

class B

sealed trait Test {
  type Meta
}

case class Test1() extends Test {
  type Meta = A
}
case class Test2() extends Test {
  type Meta = B
}

case class Info[T <: Test](t: T, m: T#Meta)

//Filters List[Info[_ <: Test]] by a generic type T <: Test and returns List[Info[T]]
def filter[T <: Test: ClassTag](lst: List[Info[_ <: Test]])(
    implicit ev: ClassTag[T#Meta]): List[Info[T]] =
  lst.collect {
    case Info(t: T, a: T#Meta) =>
      val i: Info[T] = Info[T](t, a)
      i
  }

SCASTIE DEMO

让我陷入困境的是PartialFunctioncase 是否详尽无遗。我尝试完全模式匹配Info[_ &lt;: Test],如下所示:

二。

val t: Info[_ <: Test] = ???
t match {
  case Info(t: Test1, a: Test1#Meta) =>
    println("1")
  case Info(t: Test2, a: Test2#Meta) =>
    println("2")
}

SCASTIE DEMO

并收到以下(非常可怕的)警告:

match may not be exhaustive.
It would fail on the following inputs: 
Info((x: _$2 forSome x not in (Test1, Test2)), (x: _$2#Meta forSome x not in (A, B))), 
Info((x: _$2 forSome x not in (Test1, Test2)), ??), Info((x: _$2 forSome x not in (Test1, Test2)), A()), 
Info((x: _$2 forSome x not in (Test1, Test2)), B()), Info(??, (x: _$2#Meta forSome x not in (A, B))), 
Info(Test1(), (x: _$2#Meta forSome x not in (A, B))), 
Info(Test1(), B()), Info(Test2(), (x: _$2#Meta forSome x not in (A, B))),   
Info(Test2(), A())

问题:在这种情况下的filter 实现是否在语义方面是正确的还是遗漏了一些奇怪的cases ?

【问题讨论】:

  • 另请注意,在第一节中,collect 函数中的match 不必是详尽的match,因为不匹配的元素将被过滤掉.

标签: scala generics type-safety


【解决方案1】:

嗯,如您所见,您有一个建议的破坏案例列表。例如,Info(Test1(), B())。让我们构建其中一个。

def foo(t: Test)(meta: t.Meta) = ??? 中的路径相关类型不同,在case class Info 中使用它们时键入投影不会编码T = Test1 =&gt; T#Meta = A 的含义。虽然我需要一个简单的实用方法来说服 scalac 推断出我想要的类型。

def castMeta[T <: Test](t: T#Meta): Test#Meta = t

有了它,我们可以从 A 和 B 中获取类型为 Test#Meta 的值。

所以,

val t: Info[_ <: Test] = Info[Test](Test1(), castMeta[Test2](new B))

会在运行时产生MatchError,并使其不涉及任何“黑客”,如空值、asInstanceOfs 或ClassTags。

另请注意,由于健全性问题,Type#Member 形式的一般类型投影计划在 Scala 3 中删除。

编辑:请注意,您的过滤器可能会执行您想要的操作,因为您自己提供目标类型对,只是您的模型完全允许 ​​scalac 警告您的奇怪情况: )

【讨论】:

  • 感谢您的回答。我尝试使用路径相关类型将case class Info[T &lt;: Test](t: T, m: T#Meta) 重写为case class Info[T &lt;: Test](t: T, m: t.Meta) 并得到错误Implementation restriction: case classes cannot have dependencies between parameters。您能想象将此类代码移植到 Scala 3 的任何解决方法吗?
  • 您可以使用带有自定义 unapply 的普通类,或者做一些完全不同的事情并使用例如match types。虽然我建议完全放弃运行时匹配并使用带有两种情况的普通枚举。
  • 我喜欢你使用匹配类型的方法。看起来很酷。
猜你喜欢
  • 2011-11-04
  • 1970-01-01
  • 1970-01-01
  • 2013-08-24
  • 1970-01-01
  • 1970-01-01
  • 2021-05-03
  • 2020-06-15
  • 1970-01-01
相关资源
最近更新 更多