Scala 类型推理规则是否优于逆变?

Scala type inference rule over contravarience?

我正在使用 Circe 并注意到一些我不太适应的事情,并且想了解引擎盖下发生了什么?

从根本上说,这不是真正的循环问题。另外,我只是在玩圆圈来测试一些东西。所以本可以直接在 JsonObject 中解码,但这不是重点。

val jobjectStr = """{
    |    "idProperty": 1991264,
    |    "nIndex": 0,
    |    "sPropertyValue": "0165-5728"
    |  }""".stripMargin



val jobject = decode[Json](jobjectStr).flatMap{ json =>
    json.as[JsonObject]
}

我的问题是 Either 的 flapMap 签名、逆变以及这里发生的事情:

我们有以下类型:

decode[Json](jobjectStr): Either[Error, Json]
json.as[JsonObject]: Decoder.Result[JsonObject]

圆环定义

final type Result[A] = Either[DecodingFailure, A]

sealed abstract class DecodingFailure(val message: String) extends Error {

现在flatMap的签名是:

def flatMap[A1 >: A, B1](f: B => Either[A1, B1]): Either[A1, B1]

换句话说,只谈论类型就像我的代码所做的那样

Either[Error, Json] flatMap Either[DecodingFailure, JsonObject]

因此我的问题是: DecodingFailure >: Error 不正确

并且确实完整表达式的类型是:

decode[Json](jobjectStr).flatMap{ json =>
    json.as[JsonObject]
}: Either[Error, JsonObject]

所以我很困惑,因为我的理解是Either的第一个参数的类型在flatMap Signature中是Contravariant的。这里似乎有一些奇怪的最小上限推理正在进行......但我不确定为什么或者是否是这种情况。

有什么解释吗?

这确实不是方差问题。 A1 >: A 只是告诉我们结果类型 A1 可能必须是接收类型 A 的 super-type,如果编译器必须去寻找一个最小上限(LUB)。 (我认为 f: B => ... 描述中 A1 的使用有点令人困惑。)

考虑以下几点:

class Base
class SubA extends Base
class SubB extends Base

Either.cond(true, "a string", new SubA)
  .flatMap(Either.cond(true, _, new SubB))

//res0: scala.util.Either[Base,String] = Right(a string)

注意结果是 Either[Base,String] 因为 BaseSubASubB.

的 LUB

所以首先,我们需要了解编译器总是会尝试推断允许编译的类型。避免编译的唯一真正方法是使用 implicits.
(不确定这是语言规范的一部分,还是编译器实现细节,还是所有编译器共有的东西,还是错误或功能)

现在,让我们从一个更简单的例子开始 List::.

sealed trait List[+A] {
  def ::[B >: A](b: B): List[B] = Cons(b, this)
}

final case class Cons[+A](head: A, tail: List[A]) extends List[A]
final case object Nil extends List[Nothing]

因此,假设编译器将始终允许像 x :: list 这样的代码始终编译。然后,我们有三种情况:

  1. x 是类型 AlistList[A],所以它是显然,返回值必须是 List[A].
  2. 类型
  3. x 是某种类型 ClistList[A],并且 CA (C <: A) 的子类型。然后,编译器简单地将 x 向上转换为 A 类型,并且该过程像前一个一样继续。
  4. x 是某种类型 D 并且 listList[A],并且 D 不是 A 的子类型。然后,编译器找到一个新类型B,它是DA之间的LUB,编译器最后将 x 转换为 B 类型,将 list 转换为 List[B] (由于协方差,这是可能的) 并像第一个一样进行。
    另外,请注意,由于 AnyNothing 等类型的存在,两种类型之间“总是”存在 LUB。

现在让我们看看 flatMap

sealed trait Either[+L, +R] {
  def flatMap[LL >: L, RR](f: R => Either[LL, RR]): Either[LL, RR]
}

final case class Left[+L](l: L) extends Either[L, Nothing]
final case clas Right[+R](r: R) extends Either[Nothing, R]

现在,假设我的左侧是 Error,我觉得这种在两个可能的左侧之间返回 LUB 的行为是最好的,因为最后我会有第一个错误,或第二个错误或最终值,因此由于我不知道这两个错误中的哪一个,那么该错误必须是某种封装了两种可能错误的类型。