为什么数组是不变的,而列表是协变的?

发布于 2024-11-23 15:49:24 字数 197 浏览 4 评论 0原文

例如,为什么可以

val list:List[Any] = List[Int](1,2,3)

工作,但

val arr:Array[Any] = Array[Int](1,2,3)

会失败(因为数组是不变的)。这个设计决策背后的预期效果是什么?

E.g. why does

val list:List[Any] = List[Int](1,2,3)

work, but

val arr:Array[Any] = Array[Int](1,2,3)

fails (because arrays are invariant). What is the desired effect behind this design decision?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(4

笑脸一如从前 2024-11-30 15:49:24

因为否则它会破坏类型安全。
如果没有,您将能够执行以下操作:

val arr:Array[Int] = Array[Int](1,2,3)
val arr2:Array[Any] = arr
arr2(0) = 2.54

并且编译器无法捕获它。

另一方面,列表是不可变的,因此您不能添加非 Int 的内容

Because it would break type-safety otherwise.
If not, you would be able to do something like this:

val arr:Array[Int] = Array[Int](1,2,3)
val arr2:Array[Any] = arr
arr2(0) = 2.54

and the compiler can't catch it.

On the other hand, lists are immutable, so you can't add something that is not Int

梦萦几度 2024-11-30 15:49:24

这是因为列表是不可变的,而数组是可变的。

This is because lists are immutable and arrays are mutable.

惟欲睡 2024-11-30 15:49:24

通常给出的答案是,可变性与协变相结合会破坏类型安全。对于收藏来说,这可以作为一个基本真理。但该理论实际上适用于任何泛型类型,而不仅仅是像 ListArray 这样的集合,而且我们根本不需要尝试和推理可变性。

真正的答案与函数类型与子类型交互的方式有关。简而言之,如果类型参数用作返回类型,那么它是协变的。另一方面,如果类型参数用作参数类型,则它是逆变的。如果它既用作返回类型又用作参数类型,则它是不变的。

让我们看一下 Array[T] 的文档 。要查看的两个明显的方法是用于查找和更新的方法:

def apply(i: Int): T
def update(i: Int, x: T): Unit

在第一个方法中 T 是返回类型,而在第二个方法中 T 是参数类型。因此,方差规则规定 T 必须是不变的。

我们可以比较 List[A] 的文档 看看为什么它是协变的。令人困惑的是,我们会发现这些方法类似于 Array[T] 的方法:

def apply(n: Int): A
def ::(x: A): List[A]

由于 A 既用作返回类型又用作参数类型,因此我们期望 A 是不变的,就像 Array[T] 的 T 一样。然而,与Array[T]不同的是,文档在::的类型上对我们撒了谎。对于大多数对此方法的调用来说,谎言已经足够好了,但不足以决定 A 的方差。如果我们展开此方法的文档并单击“完整签名”,我们就会看到真相:

def ::[B >: A](x: B): List[B]

因此 A 实际上并不显示为参数类型。相反,B(可以是 A 的任何超类型)是参数类型。这不会对 A 施加任何限制,因此它确实可以是协变的。 List[A] 上以 A 作为参数类型的任何方法都是类似的谎言(我们可以看出,因为这些方法被标记为 [use case]< /代码>)。

The normal answer to give is that mutability combined with covariance would break type safety. For collections, this can be taken as a fundamental truth. But the theory actually applies to any generic type, not just collections like List and Array, and we don't have to try and reason about mutability at all.

The real answer has to do with the way function types interact with subtyping. The short story is if a type parameter is used as a return type, it is covariant. On the other hand, if a type parameter is used as an argument type, it is contravariant. If it is used both as a return type and as an argument type, it is invariant.

Let's look at the documentation for Array[T]. The two obvious methods to look at are for the ones for lookup and update:

def apply(i: Int): T
def update(i: Int, x: T): Unit

In the first method T is a return type, while in the second T is an argument type. The rules of variance dictate that T must therefore be invariant.

We can compare the documentation for List[A] to see why it is covariant. Confusingly, we would find these methods, which are analogous to the methods for Array[T]:

def apply(n: Int): A
def ::(x: A): List[A]

Since A is used as both a return type and as an argument type, we would expect A to be invariant just like T is for Array[T]. However, unlike with Array[T], the documentation is lying to us about the type of ::. The lie is good enough for most calls to this method, but isn't good enough to decide the variance of A. If we expand the documentation for this method and click on "Full Signature", we are shown the truth:

def ::[B >: A](x: B): List[B]

So A does not actually appear as an argument type. Instead, B (which can be any supertype of A) is the argument type. This does not place any restriction on A, so it really can be covariant. Any method on List[A] which has A as an argument type is a similar lie (we can tell because these methods are marked as [use case]).

音盲 2024-11-30 15:49:24

区别在于 List 是不可变的,而 Array 是可变的。

要理解为什么可变性决定方差,请考虑制作 List 的可变版本 - 我们称之为 MutableList。我们还将使用一些示例类型:一个基类 Animal 和 2 个名为 CatDog 的子类。

trait Animal {
  def makeSound: String
}

class Cat extends Animal {
  def makeSound = "meow"
  def jump = // ...
}

class Dog extends Animal {
  def makeSound = "bark"
}

请注意,CatDog 多了一种方法 (jump)。

然后,定义一个接受可变动物列表并修改该列表的函数:

def mindlessFunc(xs: MutableList[Animal]) = {
  xs += new Dog()
}

现在,如果将猫列表传递到该函数中,将会发生可怕的事情:

val cats = MutableList[Cat](cat1, cat2)
val horror = mindlessFunc(cats)

如果我们使用粗心的编程语言,这将在编译过程中被忽略。尽管如此,如果我们只使用以下代码访问猫列表,我们的世界不会崩溃:

cats.foreach(c => c.makeSound)

但如果我们这样做:

cats.foreach(c => c.jump)

就会发生运行时错误。使用 Scala,可以防止编写此类代码,因为编译器会抱怨。

The difference is that Lists are immutable while Arrays are mutable.

To understand why mutability determines variance, consider making a mutable version of List - let's call it MutableList. We'll also make use of some example types: a base class Animal and 2 subclasses named Cat and Dog.

trait Animal {
  def makeSound: String
}

class Cat extends Animal {
  def makeSound = "meow"
  def jump = // ...
}

class Dog extends Animal {
  def makeSound = "bark"
}

Notice that Cat has one more method (jump) than Dog.

Then, define a function that accepts a mutable list of animals and modifies the list:

def mindlessFunc(xs: MutableList[Animal]) = {
  xs += new Dog()
}

Now, horrible things will happen if you pass a list of cats into the function:

val cats = MutableList[Cat](cat1, cat2)
val horror = mindlessFunc(cats)

If we were using a careless programming language, this will be ignored during compilation. Nevertheless, our world will not collapse if we only access the list of cats using the following code:

cats.foreach(c => c.makeSound)

But if we do this:

cats.foreach(c => c.jump)

A runtime error will occur. With Scala, writing such code is prevented, because the compiler will complain.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文