【问题标题】:Covariance between 3 types in ScalaScala中3种类型之间的协方差
【发布时间】:2020-09-03 15:28:52
【问题描述】:

我正在尝试查看是否有办法找到W2 类型,它是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 的超类型,以便我可以将Vectors 连接在一起并在最后得到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 stackoverflow 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[Either[Warning, Error]] 之类的东西?还是你自己的WarningOrErrorADT
  • 这实际上是一个想法。我可能会这样做。不过,我仍然想知道是否有解决方法的答案,因为它对我来说变得更加好奇:)
  • @NigelBenns 请准备好MCVE(你没有提供ValidatorResult等的定义)。还请说明您编写的 3 个代码 sn-ps 有什么问题(您如何应用 or,您如何检查 “Scala 将 W2 解析为 Any)。 “我想看看有没有办法找到一个类型W2,它是WE 两种类型的超类型” 请你说清楚你是什么正在努力实现? Any 是任意 WE 的超类型。什么是“ 超类型”?
  • 用用例更新了问题。如果需要,我可以尝试将事情简化为仅这些类型并提供完整的工作示例。

标签: scala typeclass covariance union-types


【解决方案1】:

实际上,您希望将List[A]List[B] 连接起来生成List[A | B],其中A | Bunion type

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

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

所以关于“超级简化示例”尝试一个类型类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

&lt;:!&lt; 来自这里:

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 }

【讨论】:

  • 我不得不使用 Shapless' Lub 而不是那里的那个,它仍然给我Any。但是,一旦切换到它,它就可以正常工作。谢谢!
  • @NigelBenns 好吧,老实说我忘了有shapeless.Lub :) 实际上,对我来说shapeless.Lub 似乎工作scastie.scala-lang.org/F0pWNaYSStyTEwWMKqHLpA
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2016-10-16
  • 2016-08-24
  • 2018-12-09
  • 2013-06-18
  • 2014-09-12
相关资源
最近更新 更多