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]
因为 Base
是 SubA
和 SubB
.
的 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
这样的代码始终编译。然后,我们有三种情况:
x
是类型 A 而 list
是 List[A],所以它是显然,返回值必须是 List[A]. 类型
x
是某种类型 C 而 list
是 List[A],并且 C 是 A (C <: A
) 的子类型。然后,编译器简单地将 x
向上转换为 A 类型,并且该过程像前一个一样继续。
x
是某种类型 D 并且 list
是 List[A],并且 D 不是 A 的子类型。然后,编译器找到一个新类型B,它是D和A之间的LUB,编译器最后将 x
转换为 B 类型,将 list
转换为 List[B] (由于协方差,这是可能的) 并像第一个一样进行。
另外,请注意,由于 Any 和 Nothing 等类型的存在,两种类型之间“总是”存在 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 的行为是最好的,因为最后我会有第一个错误,或第二个错误或最终值,因此由于我不知道这两个错误中的哪一个,那么该错误必须是某种封装了两种可能错误的类型。
我正在使用 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]
因为 Base
是 SubA
和 SubB
.
所以首先,我们需要了解编译器总是会尝试推断允许编译的类型。避免编译的唯一真正方法是使用 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
这样的代码始终编译。然后,我们有三种情况:
x
是类型 A 而list
是 List[A],所以它是显然,返回值必须是 List[A]. 类型
x
是某种类型 C 而list
是 List[A],并且 C 是 A (C <: A
) 的子类型。然后,编译器简单地将x
向上转换为 A 类型,并且该过程像前一个一样继续。x
是某种类型 D 并且list
是 List[A],并且 D 不是 A 的子类型。然后,编译器找到一个新类型B,它是D和A之间的LUB,编译器最后将x
转换为 B 类型,将list
转换为 List[B] (由于协方差,这是可能的) 并像第一个一样进行。
另外,请注意,由于 Any 和 Nothing 等类型的存在,两种类型之间“总是”存在 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 的行为是最好的,因为最后我会有第一个错误,或第二个错误或最终值,因此由于我不知道这两个错误中的哪一个,那么该错误必须是某种封装了两种可能错误的类型。