Scala 中 3 种类型之间的协方差

Covariance between 3 types in Scala

我想看看是否有办法找到类型 W2,它是 2 种类型 WE 的超类型。 在我的解决方案中,E 表示错误,W 表示警告。 我想要完成的是一种方法 or 如果 this 失败运行 that 并将错误移至警告类型。

这是我正在做的一个简化示例。

sealed trait Validator[-I, +E, +W, +A] extends Product with Serializable

这种类型有几种情况,在这里并不是很重要,所以我将介绍一个示例用法:

case class MyObj(coords: GeoCoords)
case class GeoCoords(lat: String, long: String)
        
// Getters
val getLatitude: Validator[GeoCoords, Nothing, Nothing, String] = from[GeoCoords].map(_.lat)
val getLongitude: Validator[GeoCoords, Nothing, Nothing, String] = from[GeoCoords].map(_.long)

val parseLatitude: Validator[GeoCoords, Exception, Nothing, Double] = getLatitude andThen nonEmptyString andThen convertToDouble
val parseLongitude: Validator[GeoCoords, Exception, Nothing, Double] = getLongitude andThen convertToDouble

现在这是一个不好的例子,但我想要做的是因为 parseLatitude 的错误类型是 Exception,也许我想给出一个默认值,但仍然明白失败了。我想将 ExceptionE 错误参数移动到 W 警告参数,如下所示:

val defaultLatitude: Validator[GeoCoords, Nothing, Nothing, Double] = success(0)

val finalLatitude: Validator[GeoCoords, Nothing, Exception, Double] = parseLatitude or defaultLatitude

但是,如果不提供默认值,我在 or 之后采取的其他操作也可能会失败,因此情况也应该如此:

val otherAction: Validator[GeoCoords, Throwable, Nothing, Double] = ???

val finalLatitude: Validator[GeoCoords, Throwable, Exception, Double] = parseLatitude or otherAction

我已经尝试在 Validator 类型上以多种方式实现 or,但每次它都会给我一个问题,基本上一直转换到 Any

def or[I2 <: I, E2 >: E, W2 >: W, B >: A](that: Validator[I2, E2, W2, B]): Validator[I2, E2, W2, B] = Validator.conversion { (i: I2) =>
  val aVal: Validator[Any, E, W, A] = this.run(i)
  val bVal: Validator[Any, E2, W2, B] = that.run(i)

  val a: Vector[W2] = aVal.warnings ++ bVal.warnings
  // PROBLEM HERE
  val b: Vector[Any] = a ++ aVal.errors

  Result(
    aVal.warnings ++ aVal.errors ++ bVal.warnings,
    bVal.value.toRight(bVal.errors)
  )
}

我需要能够说 W2WE 的超类型,这样我就可以将 Vector 连接在一起并得到在末尾键入 W2

一个超级简化的自包含示例是:

case class Example[+A, +B](a: List[A], b: List[B]) {
  def or[A2 >: A, B2 >: B](that: Example[A2, B2]): Example[A2, Any] = {
    Example(that.a, this.a ++ this.b ++ that.a)
  }
}

object Whosebug extends App {

  val example1 = Example(List(1, 2), List(3, 4))
  val example2 = Example(List(5, 6), List(7, 8))

  val example3 = example1 or example2

}

我希望 or 的输出类型为 Example[A2, B2] 而不是 Example[A2, Any]

实际上你想要 List[A]List[B] 的串联产生 List[A | B],其中 A | Bunion type.

How to define "type disjunction" (union types)?

在 Scala 中没有联合类型 2 但我们可以用类型 classes 来模拟它们。

所以关于“超级简化的例子”尝试一个类型class LUB(最小上限)

case class Example[A, B](a: List[A], b: List[B]) {
  def or[A2 >: A, B2, AB](that: Example[A2, B2])(
    implicit
    lub: LUB.Aux[A, B, AB],
    lub1: LUB[AB, A2]
  ): Example[A2, lub1.Out] = {
    Example(that.a, (this.a.map(lub.coerce1) ++ this.b.map(lub.coerce2)).map(lub1.coerce1(_)) ++ that.a.map(lub1.coerce2(_)))
  }
}

val example1: Example[Int, Int] = Example(List(1, 2), List(3, 4))
val example2: Example[Int, Int] = Example(List(5, 6), List(7, 8))

val example3 = example1 or example2
//  example3: Example[Int, Any] // doesn't compile
example3: Example[Int, Int] // compiles

trait LUB[A, B] {
  type Out
  def coerce1(a: A): Out
  def coerce2(b: B): Out
}

trait LowPriorityLUB {
  type Aux[A, B, Out0] = LUB[A, B] { type Out = Out0 }
  def instance[A, B, Out0](f: (A => Out0, B => Out0)): Aux[A, B, Out0] = new LUB[A, B] {
    override type Out = Out0
    override def coerce1(a: A): Out0 = f._1(a)
    override def coerce2(b: B): Out0 = f._2(b)
  }

  implicit def bSubtypeA[A, B <: A]: Aux[A, B, A] = instance(identity, identity)
}

object LUB extends LowPriorityLUB {
  implicit def aSubtypeB[A <: B, B]: Aux[A, B, B] = instance(identity, identity)
  implicit def default[A, B](implicit ev: A <:!< B, ev1: B <:!< A): Aux[A, B, Any] = 
    instance(identity, identity)
}

// Testing:
implicitly[LUB.Aux[Int, AnyVal, AnyVal]]
implicitly[LUB.Aux[AnyVal, Int, AnyVal]]
implicitly[LUB.Aux[Int, String, Any]]
implicitly[LUB.Aux[Int, Int, Int]]
//  implicitly[LUB.Aux[Int, AnyVal, Any]] // doesn't compile

<:!< 来自这里:

Scala: Enforcing A is not a subtype of B

https://github.com/milessabin/shapeless/blob/master/core/src/main/scala/shapeless/package.scala#L48-L52

如果你希望Example是协变的

case class Example[+A, +B]...

使LUBLUB.Aux逆变

trait LUB[-A, -B]...

type Aux[-A, -B, Out0] = LUB[A, B] { type Out = Out0 }