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

Why Scala Infer the Bottom Type when the type parameter is not specified?

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

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

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

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

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

在实例化它的地方假设为 Right[Int](2) 并且推断的类型是 Right[Nothing, Int] 并且扩展为 Either[Nothing, 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 的回答,这很有道理,我仍然理解编译器正在做什么以及为什么这样做。下面的例子说明了我的困惑

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

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

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

时才能提供 (e:Int) => e * 4

所以换句话说,我的问题是,如果以后要更改它,那么将类型固定为 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+]

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

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

trait Food
trait Fruit extends Food
trait Apple extends Fruit

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

我们可以供应

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

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

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

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

"Method foo needs a function that can take a Fruit and produce a Fruit. This means foo will have some Fruit and will need a function it can feed it to, and expect some Fruit back. If it gets a function Food => Apple, everything is fine - it can still feed it Fruit (because the function takes any food), and it can receive Fruit (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 => WhateverString => WhateverAny => Whatever 对其进行实例化。这恰恰是因为前面解释过的函数输入的逆变性。这就是您的 map 有效的原因。

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

我认为编译器将未知类型固定为 Nothing 以防出现逆变,这有点困难。当它在协方差的情况下将未知类型固定为 Any 时,感觉更自然(可以是“Anything”)。由于前面解释的协变和逆变的双重性,相同的推理适用于逆变 Nothing 和协变 Any.

引用自 Scala 中编译时和运行时元编程的统一 通过尤金·布尔马科

https://infoscience.epfl.ch/record/226166(第 95-96 页)

During type inference, the typechecker collects constraints on missing type arguments from bounds of type parameters, from types of term arguments, and even from results of implicit search (type inference works together with implicit search because Scala supports an analogue of functional dependencies). One can view these constraints as a system of inequalities where unknown type arguments are represented as type variables and order is imposed by the subtyping relation.

After collecting constraints, the typechecker starts a step-by-step process that, on each step, tries to apply a certain transformation to inequalities, creating an equivalent, yet supposedly simpler system of inequalities. The goal of type inference is to transform the original inequalities to equalities that represent a unique solution of the original system.

Most of the time, type inference succeeds. In that case, missing type arguments are inferred to the types represented by the solution.

However, sometimes type inference fails. For example, when a type parameter T is phantom, i.e. unused in the term parameters of the method, its only entry in the system of inequalities will be L <: T <: U, where L and U are its lower and upper bound respectively. If L != U, this inequality does not have a unique solution, and that means a failure of type inference.

When type inference fails, i.e. when it is unable to take any more transformation steps and its working state still contains some inequalities, the typechecker breaks the stalemate. It takes all yet uninferred type arguments, i.e. those whose variables are still represented by inequalities, and forcibly minimizes them, i.e. equates them to their lower bounds. This produces a result where some type arguments are inferred precisely, and some are replaced with seemingly arbitrary types. For instance, unconstrained type parameters are inferred to Nothing, which is a common source of confusion for Scala beginners.

您可以了解有关 Scala 中类型推断的更多信息:

Hubert Plociniczak 解密本地类型推断 https://infoscience.epfl.ch/record/214757

Guillaume Martres Scala 3,类型推断和你! https://www.youtube.com/watch?v=lMvOykNQ4zs

Guillaume Martres Dotty 和类型:到目前为止的故事 https://www.youtube.com/watch?v=YIQjfCKDR5A

幻灯片http://guillaume.martres.me/talks/

Aleksander Boruch-Gruszecki Dotty 中的 GADT https://www.youtube.com/watch?v=VV9lPg3fNl8