Ковариантное безопасное литье в Scala - PullRequest
1 голос
/ 15 января 2020

Я пытаюсь написать безопасное приведение для класса с ковариантным параметром типа, например:

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

Он заполняется динамическим c внешним источником данных, но я по крайней мере хочу Статически убедитесь, что B является подклассом A, а затем верните None, если сбой при типизации. Я продолжаю получать сообщения о том, что A находится в ковариантной позиции. Я могу получить его для проверки типов, отключив <: A, но как я могу указать эту гарантию c о B?

Я знаю, что это предотвращает ситуации, такие как присвоение Foo[Dog] Foo[Animal] val, затем пытается сделать что-то вроде safeCast[Mammal], где Mammal не является подтипом Dog. Это на самом деле нормально в моем случае. Даже если разрешить приведение к супертипу Animal, все будет в порядке. В основном я хочу статически запретить кому-либо пытаться safeCast[Plant].

Обратите внимание, что я могу получить его для проверки типов с помощью функции, внешней по отношению к классу, как показано ниже, но мне нужен метод для класса.

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

В качестве бонуса, если вы знаете способ реализовать это, используя кошек или что-то без isInstanceOf, это было бы очень полезно.

1 Ответ

5 голосов
/ 15 января 2020

А как насчет класса типов подхода.

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 это просто простые типы. Если они относятся к типам с более высоким родом, таким как Список , у вас могут возникнуть ошибки времени выполнения.

...