gpt4 book ai didi

scala - Scala中的泛型不变量协变对变量

转载 作者:行者123 更新时间:2023-12-04 18:56:58 26 4
gpt4 key购买 nike

这可能是一个非常愚蠢的问题,但是即使长时间挠头,我也无法理解区别。

我正在浏览Scala泛型的页面:https://docs.scala-lang.org/tour/generic-classes.html

在这里,据说


注意:泛型类型的子类型化是不变的。这意味着如果我们
拥有一堆类型为Stack [Char]的字符,则无法使用
作为类型为Stack [Int]的整数堆栈。这是不合理的,因为
这将使我们能够在字符堆栈中输入真实的整数。至
结论是,当且仅当B = A时,Stack [A]只是Stack [B]的子类型。


我完全理解我不能在需要Char的地方使用Int
但是,我的Stack类仅接受A类型(即invariant)。如果我将苹果,香蕉或水果放入其中,它们都会被接受。

class Fruit

class Apple extends Fruit

class Banana extends Fruit

val stack2 = new Stack[Fruit]
stack2.push(new Fruit)
stack2.push(new Banana)
stack2.push(new Apple)


但是,在下一页( https://docs.scala-lang.org/tour/variances.html)上,它说类型参数应该是协变 +A,那么即使在水果示例中添加了 invariant子类型,Fruit示例如何工作。

希望我清楚我的问题。让我知道更多信息。需要添加。

最佳答案

这根本与方差无关。

您声明stack2Stack[Fruit],换句话说,您声明允许您将任何东西放入StackFruitAppleFruit的子类型,因此您可以将Apple放入StackFruits中。

这称为子类型化,与差异完全无关。

让我们退后一步:差异实际上是什么意思?

好吧,差异就是“变化”(例如“变化”或“可变”之类的词)。 co-表示“在一起”(认为是合作,共同教育,共处一地),contra-表示“反对”(认为是矛盾,反情报,反叛乱,避孕),in-表示“无关”或“非”(认为是非自愿的,难以接近的,不宽容的)。

因此,我们拥有“变化”,并且该变化可以是“一起”,“反对”或“无关”。好吧,为了进行相关的更改,我们需要两个更改的内容,它们既可以一起更改(即,当一个内容更改时,另一内容也“沿同一方向”更改),它们可以相互更改(即当一件事物发生变化时,另一事物发生“相反的变化”),或者它们可以不相关(即,一件事物发生变化时,另一事物没有变化)。

以上就是协方差,逆方差和不变性的数学概念。我们需要的只是两个“事物”,一个“变化”的概念,而这个变化需要一个“方向”的概念。

现在,这当然是非常抽象的。在这种特定情况下,我们正在谈论子类型化和参数多态性的上下文。这在这里如何适用?

好吧,我们两件事是什么?当我们有诸如C[A]之类的type constructor时,我们的两件事是:


类型参数A
构造的类型是将类型构造函数C应用于A的结果。


方向感方面的变化是什么?是subtyping

因此,现在的问题变成:“当我将A更改为B(沿着子类型的方向之一,即使其成为子类型或超类型)时,C[A]C[B]有何关系?” 。

同样,存在三种可能性:


CovarianceA <: BC[A] <: C[B]:当AB的子类型时,C[A]C[B]的子类型,换句话说,当我沿子类型层次结构更改A时,然后C[A]A朝同一方向变化。
ContravarianceA <: BC[A] :> C[B]:当AB的子类型时,则C[A]C[B]的超类型,换句话说,当我沿子类型层次结构更改A时,然后C[A]在相反的方向上相对于A更改。
不变性:C[A]C[B]之间没有子类型关系,两者都不是子类型或父类型。


您现在可能会问自己两个问题:


为什么这有用?
哪一个是正确的?


出于相同的原因,这很有用。实际上,这只是子类型化。因此,如果您使用的语言同时具有子类型和参数多态性,那么了解一种类型是否为另一种类型的子类型非常重要,而方差则可以告诉您构造类型是否是另一种构造类型的子类型。基于类型参数之间子类型关系的相同构造函数。

哪一个是正确的比较棘手,但值得庆幸的是,我们有一个功能强大的工具来分析子类型何时是另一种类型的子类型:Barbara Liskov's Substitution Principle告诉我们类型S是类型T的子类型可以将T实例替换为S实例,而无需更改程序的可观察期望属性。

我们来看一个简单的泛型类型,一个函数。一个函数有两个类型参数,一个用于输入,一个用于输出。 (我们在这里保持简单。)F[A, B]是一个接受类型为A的参数并返回类型为B的结果的函数。

现在,我们经历几个场景。我有一些操作O想要使用从Fruit s到Mammal s的函数(是的,我知道,令人兴奋的原始示例!)LSP表示我也应该能够传递该函数的子类型。 ,一切仍然应该正常进行。假设FA中是协变的。然后,我应该也可以将功能从Apple传递给Mammal。但是,当O将Orange传递给F时会发生什么?应该允许的! O能够将Orange传递给F[Fruit, Mammal],因为OrangeFruit的子类型。但是,来自Apple的函数不知道如何处理Orange,因此它会崩溃。 LSP说它应该工作,但这意味着我们可以得出的唯一结论是我们的假设是错误的:F[Apple, Mammal]不是F[Fruit, Mammal]的子类型,换句话说,FA中不是协变的。

如果是反变的怎么办?如果将F[Food, Mammal]传递给O怎么办?好吧,O再次尝试传递Orange并起作用:OrangeFood,因此F[Food, Mammal]知道如何处理Orange。现在我们可以得出结论,函数的输入是互斥的,即,您可以传递采用更通用类型的函数作为其输入,以代替采用更受限类型的函数,并且一切都会正常进行。

现在让我们看一下F的输出。如果FB中是正变的,就像在A中一样,会发生什么?我们将F[Fruit, Animal]传递给O。根据LSP,如果我们是对的,并且函数的输出相反,则不会发生任何不良情况。不幸的是,O在getMilk的结果上调用F方法,但是F刚返回了它一个Chicken。哎呀。因此,函数的输出不能互变。

OTOH,如果我们通过F[Fruit, Cow]会怎样?一切仍然有效! O在返回的母牛身上呼叫getMilk,它的确提供了牛奶。因此,函数的输出看起来是协变的。

这是适用于方差的一般规则:


在LSP中将C[A]协变是安全的(就LSP而言),IFF A仅用作输出。
从LSP的角度来看,使A互变是安全的(IFP C[A]仅用作输入)。
如果A可以用作输入或输出,则AA中必须是不变的,否则结果不安全。


实际上,这就是为什么C♯的设计师选择对variance annotationsKotlin uses those same keywords重新使用已经存在的关键字C[A]A的原因。

因此,例如,不可变集合通常可以在其元素类型上协变,因为它们不允许您将某些内容放入集合中(您只能构造具有潜在不同类型的新集合),而只能取出元素。因此,如果我想获取数字列表,并且有人将整数列表交给我,那很好。

另一方面,请考虑一个输出流(例如in),您只能在其中放入内容而不能将其取出。为此,可以放心。即如果我希望能够打印字符串,并且有人将可打印任何对象的打印机交给我,那么它也可以打印字符串,我很好。其他示例是比较函数(您只放入泛型,输出固定为布尔值或枚举或整数,或您的特定语言选择的任何设计)。或谓词,它们只有通用输入,输出始终固定为布尔值。

但是,例如,可变集合只能在类型不变的情况下保证类型安全,在可变集合中既可以放入东西也可以取出东西。例如,有许多教程详细解释了如何使用它们的协变可变数组来破坏Java或C♯的类型安全性。

但是请注意,一旦进入更复杂的类型,类型是输入还是输出并不总是很明显。例如,当您的类型参数用作抽象类型成员的上限或下限时,或者当您的方法使用的函数返回一个参数类型为您的类型参数的函数时。

现在,回到您的问题:您只有一堆。您永远不会问一个堆栈是否是另一个堆栈的子类型。因此,差异在您的示例中不起作用。

关于scala - Scala中的泛型不变量协变对变量,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/50303079/

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