Scala 中的协变安全转换

Kar*_*ldt 1 generics casting scala covariance

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

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

它由动态外部数据源填充,但我至少想静态地确保它B是 的子类A,然后None在类型转换失败时返回 a 。我不断收到关于A处于协变位置的错误。我可以得到它通过不使用的要进行类型检查<: A,但我怎么能指定有关此静态保证B

我知道它会阻止诸如将 a 分配Foo[Dog]Foo[Animal]val 之类的情况,然后尝试执行诸如safeCast[Mammal]whereMammal不是Dog. 就我而言,这实际上没问题。即使允许投射到超类型 ofAnimal也可以。我主要想静态地防止有人尝试safeCast[Plant].

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

def safeCast[A, B <: A](foo: Foo[A]): Option[B] = ???
Run Code Online (Sandbox Code Playgroud)

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

Lui*_*rez 5

类型类方法怎么样。

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)
    }
}
Run Code Online (Sandbox Code Playgroud)

可以这样使用:

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
Run Code Online (Sandbox Code Playgroud)

两个重要的注意事项:

  • 这里的技巧是将类外的方法定义为扩展方法。
    (在这一点上,人们甚至可以考虑完全删除类型类,而只在扩展方法上使用隐式证据和类标签本身)

  • 由于使用了 classtag,您应该注意AB都只是普通类型。如果它们是像List这样更高级的类型,您可能最终会遇到运行时错误。