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



我想知道是否有人可以在下面解释这种特殊情况下的推理规则,最重要的是它是理性/暗示?

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

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

只是为了给出一些上下文,这与 Both(二)的定义以及它如何适用于左和右有关。两者都是根据模式定义的

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)

因此,当它是逆变时,我们确实有同样的东西,这使得所有行为或推理规则是连贯的。但是我不确定这样做的理由....

为什么不采用相反的规则,即推断Any当协变/不变时,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的类型为 Local[String,Int]

给定映射定义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+]

一般来说,当一个类型被放置在函数/方法参数中时,这意味着它处于所谓的"逆变位置"。如果它在函数/方法返回值中使用,则处于所谓的"协变位置"。如果两者兼而有之,那么它是不变的。

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

trait Food
trait Fruit extends Food
trait Apple extends Fruit
def foo(someFunction: Fruit => Fruit) = ???

我们可以供应

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

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

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

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

"方法foo需要一个函数,可以接受Fruit并产生Fruit.这意味着foo将有一些Fruit,并且需要 函数它可以馈送它,并期望一些Fruit返回。如果它得到一个 功能Food => Apple,一切都很好 - 它仍然可以喂它Fruit(因为该功能接受任何食物),它可以接收Fruit(苹果是水果,所以合同是尊重的)。

回到你最初的困境,希望这可以解释为什么在没有任何额外信息的情况下,编译器会为协变类型采用尽可能低的类型,而对逆变类型采用最高可能类型。如果我们想提供一个函数foo,我们知道有一个肯定有效的:Any => Nothing

一般差异。

Scala 文档中的差异。

关于 Scala 中差异的文章(完全披露:我写的)。

编辑:

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

当你实例化一个Left[String, Nothing]时,你可以稍后用函数Int => WhateverString => WhateverAny => Whatevermap它。这主要是因为前面解释的函数输入的逆变性。这就是您的map起作用的原因。

"如果要更改类型,将类型固定为 Nothing 有什么意义 后来呢?

我认为在逆变的情况下,将未知类型固定为Nothing有点困难。当它在协方差的情况下将未知类型固定为Any时,感觉更自然(它可以是"任何")。由于前面解释的协变和逆变的二元性,相同的推理适用于逆变Nothing和协变Any

这是引用自Scala中编译时和运行时元编程的统一,作者:Eugene Burmako

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

在类型推断期间,类型检查器收集缺少的约束 类型参数边界的类型参数,术语的类型 参数,甚至来自隐式搜索的结果(类型推断 与隐式搜索一起工作,因为 Scala 支持模拟 的功能依赖关系)。可以将这些约束视为 不等式系统,其中未知类型参数表示为 类型变量和顺序由子类型关系强加。

收集约束后,类型检查器将逐步启动 在每个步骤中尝试将某种转换应用于的过程 不平等,创造了一个等价的,但据说更简单的系统 不等式。类型推断的目标是转换原始 不等式到代表独特解决方案的平等 原始系统。

大多数情况下,类型推断会成功。在那 案例中,缺少的类型参数被推断为由 解决方案。

但是,有时类型推断会失败。例如 当类型参数T是幻像时,即在术语参数中未使用 在该方法中,它在不平等系统中的唯一条目将是L <: T <: U,其中LU分别是其下限和上限。 如果L != U,这种不等式没有唯一的解决方案,并且 表示类型推断失败。

当类型推断失败时,即 当它无法采取任何更多的转换步骤时,其 工作状态仍然包含一些不等式,类型检查器中断 僵持不下。它接受所有尚未推断的类型参数,即那些 其变量仍然由不平等表示,并且被迫最小化它们,即将它们等同于它们的下限。这会产生 精确推断某些类型参数的结果,以及一些 替换为看似任意的类型。例如 不受约束的类型参数推断为Nothing,即 Scala 初学者的常见困惑来源。

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

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

纪尧姆·马特雷斯·斯卡拉 3,类型推理和你!https://www.youtube.com/watch?v=lMvOykNQ4zs

纪尧姆·马特雷斯多蒂和类型:到目前为止的故事 https://www.youtube.com/watch?v=YIQjfCKDR5A

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

亚历山大·博鲁克-格鲁谢基GADTs in Dottyhttps://www.youtube.com/watch?v=VV9lPg3fNl8

最新更新