【问题标题】:Scala type inference rule over contravarience?Scala类型推理规则对逆变?
【发布时间】:2020-09-26 06:37:41
【问题描述】:

我在使用 Circe 并注意到一些我不太熟悉的东西,并想了解引擎盖下发生了什么?

从根本上说,这并不是一个真正的问题。我也只是在玩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]

circe 定义的地方

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 签名中是逆变的。这里似乎正在进行一些奇怪的最小上限推理......但我不确定为什么或是否是这种情况。

有什么解释吗?

【问题讨论】:

  • 首先,Either 在它们的两个类型参数上都是协变

标签: scala functional-programming type-inference circe


【解决方案1】:

这确实不是方差问题。 A1 >: A 只是告诉我们结果类型A1 可能必须是接收类型A 的超类型,如果编译器必须去寻找最小上限(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。

【讨论】:

  • 超类型的概念应该是有意义的,因为如果你从一开始就失败了,你不希望最后有人在子类型失败时调用函数。所以我想这是有道理的。我不明白的是应用的规则并推断出最小的上限!换言之,LUB 规则何时适用
  • 换种说法:LUB 何时应用 vs 抛出错误。例如,我只是注意到对于 scala SeqLike prepend:def +:[B >: A, That](elem: B)(implicit bf: CanBuildFrom[Repr, B, That]): That = 显然 B 是逆变的。但这里 LUB 将适用。
  • 听起来第一个评论可能是,```def +:[B >: A, That]`` 通常在容器中找到,它们的类型参数是协变的,他们需要能够拥有使用其类型参数的方法,因为方法参数始终是逆变的。 FunctionN 在类型参数中只是简单的逆变,因此不会发生推断。但是 SeqLike 在类型参数中是 Convariant 的,所以这种方法是必要的,编译器可能不得不求助于 LUB 推断。到目前为止我能得出的唯一推论:)
  • 现在,为什么 scala 编译器允许在 Co-Variant Container 中使用 LUB,其中方法类型参数是 ConTravariant,而在 Container 中,其类型参数只是逆变,这将是我的百万美元问题:)
【解决方案2】:

所以首先,我们需要了解编译器总是会尝试推断允许编译的类型。避免编译某些东西的唯一真正方法是使用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. xA 类型,listList[A],所以很明显返回值必须是 List[ A]
  2. x 是某种 C 类型,listList[A]CA 的子类型 (C <: A)。然后,编译器简单地将 x 向上转换为 A 类型,然后继续执行前一个过程。
  3. x 属于某种类型 DlistList[A],而 D 不是 的子类型一个。然后,编译器找到了一个新类型B,它是DA之间的LUB,编译器最终将x向上转换为键入 Blist 成为 List[B] (这可能是由于协方差) 并像第一个一样继续。
    另请注意,由于存在 AnyNothing 等类型,因此在两种类型之间“始终”存在 LUB。

现在让我们看看 EitherflatMap

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]

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

【讨论】:

  • 非常中肯的解释!非常感谢。这是我同时在书中做了一些研究:Programming scala, 2nd,作者提到至少在 scala 2.11 中,你会收到一个向上转换的警告!听起来像 scala 2.12 删除了它,并且执行您想要的唯一方法是坚定地声明类型以阻止不需要的推理。有任何想法为什么删除此警告?
  • 不确定,我也不记得有这样的警告。如果推断的类型是 Any 则有一个,因为推断 Any 可能总是一个错误。但是任何其他类型大部分时间都可以,如果它不是一个好的类型,你会在某个地方得到另一个编译错误。
  • 来自本书:While convenient, inferring a broader, LUB type can be a surprise if you thought you were not changing from the original type parameter. That’s why Scala 2.11 added a warning when an expression infers a broad LUB type.
  • 那将是 2.11。我在 2.12 中没有看到。或 2.13 scala> 0.1 +: Seq(2, 3) <console>:9: warning: a type was inferred to be `AnyVal`; this may indicate a programming error. 0.1 +: res0 ^ res1: Seq[AnyVal] = List(0.1, 1, 2, 3)
  • 好像是-Xlint:infer-any
猜你喜欢
  • 2013-11-29
  • 2020-10-28
  • 2020-01-25
  • 2011-11-07
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多