【问题标题】:Covariant safe cast in ScalaScala中的协变安全转换
【发布时间】:2020-05-02 07:59:15
【问题描述】:

我正在尝试为具有协变类型参数的类编写安全转换,例如:

case class Foo[+A](a: A) {
  def safeCast[B <: A](): Option[B] = ???
}

它由动态外部数据源填充,但我至少想静态确保B 是A 的子类,如果类型转换失败则返回None。我不断收到关于 A 处于协变位置的错误。我可以通过省略&lt;: A 对其进行类型检查,但是如何指定关于B 的静态保证?

我知道它可以防止将Foo[Dog] 分配给Foo[Animal] val 等情况,然后尝试执行safeCast[Mammal] 之类的操作,其中Mammal 不是Dog 的子类型。在我的情况下,这实际上没问题。即使允许转换为 Animal 的超类型也可以。我主要是想静态地阻止有人尝试safeCast[Plant]。

注意,我可以使用类外部的函数对其进行类型检查,如下所示,但我想要类上的方法。

def safeCast[A, B <: A](foo: Foo[A]): Option[B] = ???

作为奖励,如果您知道使用猫或不使用 isInstanceOf 的东西来实现此功能的方法,那将非常有用。

【问题讨论】:

  • 麻烦的是,Foo[A] &lt;: Foo[Any] 和 B &lt;: Any 也是如此,所以无论A 和 B 是什么,你的方法都将始终适用。

标签: scala generics casting covariance


【解决方案1】:

typeclass 方法怎么样?

import scala.reflect.ClassTag

@annotation.implicitNotFound("${B} is not a subtype of ${A}")
sealed trait Caster[A, B] {
  def safeCast(a: A): Option[B]
}

object Caster {
  implicit def subtypeCaster[A, B](implicit ev: B <:< A, ct: ClassTag[B]): Caster[A, B] =
    new Caster[A, B] {
      override final def safeCast(a: A): Option[B] =
        ct.unapply(a)
    }
}

可以这样使用:

sealed trait Animal
final case class Dog(name: String) extends Animal
final case class Cat(name: String) extends Animal

final case class Foo[+A](a: A)

implicit class FooOps[A] (private val foo: Foo[A]) extends AnyVal {
  @inline
  final def safeCastAs[B](implicit caster: Caster[A, B]): Option[Foo[B]] =
    caster.safeCast(foo.a).map(b => Foo(b))
}

val animal: Foo[Animal] = Foo(Dog("luis"))

animal.safeCastAs[Dog] 
// res: Option[Foo[Dog]] = Some(Foo(Dog("luis")))

animal.safeCastAs[Cat] 
// res: Option[Foo[Cat]] = None

animal.safeCastAs[String] 
// compile error: String is not a subtype of Animal

两个重要说明:

  • 这里的诀窍是将类外部的方法定义为扩展方法。
    (此时甚至可以考虑完全删除类型类,只使用隐式证据和类标签本身关于扩展方法)。

  • 由于使用了 classtag,您应该注意 A 和 B 都只是普通类型。如果它们是诸如 List 之类的更高种类的类型,您最终可能会遇到运行时错误。

【讨论】:

  • 这可能会解决 OP 的问题,但它实际上并没有与 Foo 交互,因此绕过了协方差问题。当你打电话给animal.safeCastAs[Dog] 时,animal 被输入了Animal,所以你只是在问是否Dog &lt;: Animal。如果你在一个范围内,而不是 Animal 你有一个协变 +A,我认为它不会起作用。
  • @gandaliter 你说得对,我忘了Foo。我已经编辑了答案。
  • 有道理。您仍然有问题,animal 可以输入为Foo[Any],然后有人可以调用animal.safeCastAs[Plant],但我不确定有什么办法可以解决这个问题。
  • @gandaliter 在这一点上没有什么可做的,如果你有一个 Any 任何东西都是它的有效子类型。
  • 没错。我认为这可能是回答所写问题的最佳方法。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2015-04-16
  • 2012-08-02
  • 2015-02-09
  • 2015-11-13
  • 2011-01-13
  • 1970-01-01
相关资源
最近更新 更多