vij*_*ani 5 scala scala-collections
这可能是一个非常愚蠢的问题,但即使在长时间挠头之后我也无法理解这种差异.
我正在浏览scala泛型页面:https://docs.scala-lang.org/tour/generic-classes.html
据说这里
注意:泛型类型的子类型是不变的.这意味着如果我们有一堆Stack [Char]类型的字符,那么它就不能用作Stack [Int]类型的整数堆栈.这将是不合理的,因为它将使我们能够在字符堆栈中输入真正的整数.总之,Stack [A]只是Stack [B]的子类型,当且仅当B = A时.
我完全理解这一点,我无法Char在Int需要的地方使用.但是,我的Stack班级只接受A类型(即invariant).如果我把Apple,Banana或Fruit放入其中,它们都会被接受.
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)
Run Code Online (Sandbox Code Playgroud)
但是,在下一页(https://docs.scala-lang.org/tour/variances.html)上,它说类型参数应该是协变的+A,那么即使是添加子类型,Fruit示例如何工作呢?invariant.
希望我对我的问题很清楚.如果有更多信息,请告诉我.需要添加.
这与方差完全没有关系。
您声明stack2为 a Stack[Fruit],换句话说,您声明您可以将任何内容放入Stacka 中Fruit。AnApple是 a (subtype of) Fruit,因此您可以将 anApple放入 a Stackof Fruits。
这称为子类型化,与方差完全无关。
让我们退后一步:差异实际上意味着什么?
嗯,方差的意思是“改变”(想想像“改变”或“可变”这样的词)。co-表示“在一起”(想想合作、男女同校、同地办公),contra-表示“反对”(想想矛盾、反情报、反叛乱、避孕),in-表示“无关”或“非”(想想非自愿的、无法接近的、无法容忍的)。
所以,我们有“改变”,这种改变可以是“一起”、“反对”或“不相关”。好吧,为了有相关的变化,我们需要两个变化的东西,它们可以一起变化(即当一件事发生变化时,另一件事也“向同一方向”变化),它们可以相互变化(即当一件事发生变化时,另一件事就会“向相反的方向”发生变化),或者它们可能是无关的(即,当一件事发生变化时,另一件事不会发生变化。)
这就是协方差、逆变和不变性的数学概念的全部内容。我们需要的只是两个“事物”,一些“变化”的概念,而这种变化需要一些“方向”的概念。
现在,这当然非常抽象。在这个特定的例子中,我们谈论的是子类型和参数多态性的上下文。这如何适用于这里?
那么,我们的两件事是什么?当我们有一个类型构造函数,比如C[A],那么我们要做的两件事是:
A。C来A。我们在方向感上的变化是什么?这是子类型!
所以,现在的问题变成了:“当我更改A为B(沿着子类型的方向之一,即使其成为子类型或超类型)时,那么如何C[A]与C[B]“相关联”。
再次,有三种可能性:
A <: B?C[A] <: C[B]: whenA是BthenC[A]的子类型C[B],换句话说,当我A沿着子类型层次C[A]变化时 ,然后A在相同的方向上变化。A <: B?C[A] :> C[B]:当A是的子类型B,则C[A]是一个超型的C[B],换句话说,当我改变A沿子类型的层次结构,则C[A]改变对 A在相反方向上。C[A]和之间没有子类型关系C[B],也不是另一个的子类型或超类型。你现在可能会问自己两个问题:
这与子类型有用的原因相同。事实上,这只是子类型化。因此,如果您的语言同时具有子类型化和参数多态性,那么重要的是要知道一种类型是否是另一种类型的子类型,而方差会告诉您一个构造类型是否是另一个构造类型的子类型基于类型参数之间的子类型关系的相同构造函数。
哪个是正确的比较棘手,但幸运的是,我们有一个强大的工具来分析一个子类型何时是另一种类型的子类型:Barbara Liskov 的替换原则告诉我们,一个类型S是T IFF类型的子类型,任何实例都T可以被替换在S不改变程序可观察到的理想属性的情况下使用实例。
让我们采用一个简单的泛型类型,一个函数。一个函数有两个类型参数,一个用于输入,一个用于输出。(我们在这里保持简单。)F[A, B]是一个函数,它接受一个 type 的参数A并返回一个 type 的结果B。
现在我们玩几个场景。我有一些操作O想要处理从Fruits 到Mammals的函数(是的,我知道,令人兴奋的原始示例!)LSP 说我也应该能够传入该函数的子类型,并且一切都应该仍然有效. 假设,F在 中是协变的A。然后我也应该能够将函数从Apples传递到Mammals。但是当O传递一个Orangeto时会发生什么F?应该允许!O能够传递OrangetoF[Fruit, Mammal]因为它Orange是 的子类型Fruit。但是,来自Apples的函数不知道如何处理Oranges,所以它爆炸了。LSP 说它应该可以工作,这意味着我们可以得出的唯一结论是我们的假设是错误的:F[Apple, Mammal]不是 的子类型F[Fruit, Mammal],换句话说,F不是 的协变A。
如果它是逆变的呢?如果我们将 an 传递F[Food, Mammal]给O 会怎样?好吧,O再次尝试传递 anOrange并且它起作用了:Orangeis a Food,所以F[Food, Mammal]知道如何处理Oranges 。我们现在可以得出结论,函数在其输入中是逆变的,即您可以传递一个将更通用类型作为其输入的函数,以替代接受更受限制类型的函数,一切都会好起来的。
现在让我们看看F. 如果FinB就像in 是逆变的会发生什么A?我们将 an 传递F[Fruit, Animal]给O。根据 LSP,如果我们是对的并且函数的输出是逆变的,那么应该不会发生任何不好的事情。不幸的是,OgetMilk在 的结果上调用了该方法F,但F只是返回了一个Chicken。哎呀。因此,函数的输出不能是逆变的。
OTOH,如果我们通过一个会发生什么F[Fruit, Cow]?一切仍然有效!O呼唤getMilk返回的母牛,它确实产奶了。因此,看起来函数的输出是协变的。
这是适用于方差的一般规则:
C[A] 协在A IFF A使用仅作为输出。C[A] 逆变在A IFF A使用仅作为输入。A可以用作输入或输出,则在 中C[A] 必须是不变的A,否则结果是不安全的。事实上,这就是为什么C 2的设计师选择重新使用现有的关键字in并out进行方差注释和科特林使用这些相同的关键字。
因此,例如,不可变集合的元素类型通常可以是协变的,因为它们不允许您将某些内容放入集合中(您只能构造一个具有潜在不同类型的新集合),而只能将元素取出。所以,如果我想得到一个数字列表,而有人递给我一个整数列表,我就可以了。
另一方面,想想一个输出流(例如 a Logger),在那里你只能把东西放进去,但不能把它拿出来。为此,逆变是安全的。即,如果我希望能够打印字符串,并且有人递给我一台可以打印任何对象的打印机,那么它也可以打印字符串,我很好。其他示例是比较函数(您只将泛型放入,输出固定为布尔值或枚举或整数或您的特定语言选择的任何设计)。或者谓词,它们只有通用输入,输出总是固定为布尔值。
但是,例如,可变集合,您可以在其中放入和取出东西,只有在它们不变时才是类型安全的。例如,有很多教程详细解释了如何使用协变可变数组来破坏 Java 或 C 的类型安全性。
但是请注意,一旦您了解更复杂的类型,类型是输入还是输出并不总是很明显。例如,当您的类型参数用作抽象类型成员的上限或下限时,或者当您有一个方法接受一个函数,该函数返回一个函数,其参数类型是您的类型参数。
现在,回到你的问题:你只有一个堆栈。你永远不会问一个堆栈是否是另一个堆栈的子类型。因此,方差在您的示例中不起作用。
| 归档时间: |
|
| 查看次数: |
320 次 |
| 最近记录: |