gpt4 book ai didi

scala - 为什么 Scala 在未指定类型参数时推断底部类型?

转载 作者:行者123 更新时间:2023-12-05 09:36:47 25 4
gpt4 key购买 nike

我想知道是否有人可以解释下面这个特殊情况下的推理规则,最重要的是它是有理/蕴涵的?

case class E[A, B](a: A) // class E
E(2) // E[Int,Nothing] = E(2)

请注意,我可以编写 E[Int](2)。对我来说重要的是为什么第二个参数类型被推断为 Nothing (即 Bottom 类型)而不是例如 Any ?为什么会这样?有什么道理/含义?

只是为了提供一些上下文,这与 Either 的定义及其对 Left 和 Right 的工作方式有关。两者都是根据模式定义的

final case class X[+A, +B](value: A) extends Either[A, B]

在实例化它的地方假设为 Right[Int](2) 并且推断的类型是 Right[Nothing, Int] 并且通过扩展 Either[什么都没有,Int]

编辑1

这里有一致性,但我仍然可以找出合理性。下面是带有逆变参数的相同定义:

case class E[A, -B](a: A)// class E
E(2) // E[Int, Any] = E(2)

因此,当它是逆变时,我们确实有相同的东西,并且使所有行为或推理规则一致。但是我不确定这样做的合理性....

为什么不使用相反的规则,即在 Co-Variant/Invariant 时推断Any,在 Contra-Variant 时推断Nothing

编辑2

根据@slouc Answer,这很有道理,我仍然理解编译器正在做什么以及为什么这样做。下面的例子说明了我的困惑

val myleft = Left("Error") // Left[String,Nothing] = Left(Error)
myleft map { (e:Int) => e * 4} // Either[String,Int] = Left(Error)

  1. 首先,编译器将类型修复为“肯定有效”的类型,以重用@slouc 的结论(尽管在函数的上下文中更有意义)Left[String,没有]
  2. 接下来编译推断 myleft 为 Either[String,Int] 类型

给定 map 定义 def map[B](f: A => B): Either[E, B], (e:Int) => e * 4 只有在 myleft 实际上是 Left[String,Int]Either[String,Int]

时才能提供

所以换句话说,我的问题是,如果以后要更改类型,那么将类型固定为 Nothing 有什么意义。

确实下面的不编译

val aleft: Left[String, Nothing] = Left[String, Int]("Error")

type mismatch;
found : scala.util.Left[String,Int]
required: Left[String,Nothing]
val aleft: Left[String, Nothing] = Left[String, Int]("Error")

那么为什么我要推断一个类型,这通常会阻止我对该类型的变量做任何其他事情(但肯定在推断方面有效),最终改变该类型,所以我可以使用该推断类型的变量做一些事情。

编辑3

Edit2 有点误会,一切都在@slouc 的回答和评论中澄清。

最佳答案

  • 协方差:
    给定类型 F[+A]和关系A <: B ,则以下成立:F[A] <: F[B]

  • 逆变:
    给定类型 F[-A]和关系A <: B ,则以下成立:F[A] >: F[B]

如果编译器无法推断出确切的类型,它将在协变的情况下解析最低可能的类型,在逆变的情况下解析最高可能的类型。

为什么?

当涉及到子类型的变化时,这是一个非常重要的规则。它可以在 Scala 的以下数据类型的示例中显示:

trait Function1[Input-, Output+]

一般来说,当一个类型被放置在函数/方法参数中时,就意味着它处于所谓的“逆变位置”。如果它用在函数/方法返回值中,它就处于所谓的“协变位置”。如果两者都存在,则它是不变的。

现在,根据本文开头的规则,我们得出结论:

trait Food
trait Fruit extends Food
trait Apple extends Fruit

def foo(someFunction: Fruit => Fruit) = ???

我们可以供应

val f: Food => Apple = ???
foo(f)

函数fsomeFunction 的有效替代品因为:

  • FoodFruit 的父类(super class)型(输入的逆变)
  • AppleFruit 的子类型(输出的协方差)

我们可以这样用自然语言来解释:

"Method foo needs a function that can take a Fruit and produce aFruit. This means foo will have some Fruit and will need afunction it can feed it to, and expect some Fruit back. If it gets afunction Food => Apple, everything is fine - it can still feed itFruit (because the function takes any food), and it can receiveFruit (apples are fruit, so the contract is respected).

回到您最初的困境,希望这可以解释为什么在没有任何额外信息的情况下,编译器将求助于协变类型的最低可能类型和逆变类型的最高可能类型。如果我们想为 foo 提供一个函数,我们知道有一个确实有效:Any => Nothing .

Variance in general .

Variance in Scala documentation .

Article about variance in Scala (完全披露:我写的)。

编辑:

我想我知道是什么让您感到困惑。

当您实例化 Left[String, Nothing] 时, 你以后可以 map它有一个功能 Int => Whatever , 或 String => Whatever , 或 Any => Whatever .这恰恰是因为前面解释过的函数输入的逆变性。这就是为什么你的 map有效。

"what is the point of fixing the type to Nothing if it is to change itlater?"

我认为编译器将未知类型修复为 Nothing 有点难在逆变的情况下。当它将未知类型修复为 Any在协方差的情况下,它感觉更自然(它可以是“任何东西”)。由于前面解释的协变和逆变的对偶性,相同的推理适用于逆变 Nothing和协变 Any .

关于scala - 为什么 Scala 在未指定类型参数时推断底部类型?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/64966219/

25 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com