问题 为什么Array [T forSome {type T; }]意味着数组[任意]


我正在读这篇文章 “Scala中的存在类型”,发现了一些我无法理解的东西:

Array[T] forSome { type T; }
Array[T forSome { type T; }]

它们看起来几乎相同,但实际上它们非常不同。第一个是所有数组的类型,无论它们的类型参数如何。第二个是Array [Any]。

为什么他们如此不同,尤其是,为什么第二个意味着什么 Array[Any]


6157
2018-03-29 16:09


起源



答案:


不同之处在于类型系统何时决定T是什么。

也许最好的答案是,尝试在某些代码中使用这两种类型,看看会发生什么,并试图找出原因。但我会首先尝试解释一些类型理论。在这种情况下:

Array[T forSome { type T; }]

在哪里 forSome 在里面 [] 括号,每个元素本身只需要以某种方式解释为 T forSome { type T; }。好的,所以 Int 是一个 T 对于某种类型 T,所以你可以放一个 Int 在数组中。一个 String 也是一个 T 对于某种类型 T,因为选择 forSome 你可以说,一次只适用于一个元素 T 是 String 这一次而不是 Int,所以你可以把 String 在数组中,即使它已经包含一个 Int

另一方面,虽然我们可以选择 T 独立于数组的任何元素,已经选择了数组本身的类型:它是可以容纳任何东西的数组类型。它不可能是一个 Array[Int] 或者 Array[String]

但在这种情况下:

Array[T] forSome { type T; }

方式 T 在阵列之外只决定一次。该阵列实际上可能是一个 Array[Int],和 Array[String]或者 Array[Any]。但一旦类型 T 如果选择,则阵列的所有元素必须与该类型一致。

好, 现在 让我们尝试一些代码。这是一个例子:

scala> var a = Array(1,2);
a: Array[Int] = Array(1, 2)

scala> def first(z : Array[T] forSome { type T }) = z(0);
first: (z: Array[_])Any

scala> def firstany(z : Array[T forSome { type T }]) = z(0);
firstany: (z: Array[T forSome { type T }])Any

scala> first(a);
res0: Any = 1

scala> firstany(a);
error: type mismatch;
 found   : Array[Int]
 required: Array[T forSome { type T }]
       firstany(a);
                ^

为什么错误?因为阵列 a 有类型 Array[Int],只能包含类型的东西 Int。那肯定是一个 Array[T] forSome {type T},因为所有类型系统需要做的是选择Int作为类型T.因此 first(a) 没关系但正如上面所解释的那样 Array[T forSome { type T }] 不可能是一个 Array[Int]所以 firstany(a) 是一个错误。

但是以下是可以的,因为一个 Array[Any] 是一个 Array[T forSome { type T }]

scala> var b = Array(1, "x");
b: Array[Any] = Array(1, x)

scala> firstany(b);
res1: Any = 1

6
2018-03-29 18:41





改变括号的位置可能会改变很多意义,这不足为奇。有点像你切换∃和∀

第一个意思是:有一些类型 T 这是一个 Array[T]。所以这很满意 Array[String]Array[Int]等数组是同质的,但我们不知道哪种类型。

第二个意思是:对于数组的每个元素,都有一些类型 T,使元素属于类型 T。这根本没有约束,所以它只是 Array[Any]。有了 forSome 在方括号内,如果你这样做,可能会很有用

Array[Set[T] forSome {type T}]

这意味着数组的元素都是集合,但在同一个数组中可能同时存在一组Int和一组字符串。


关于Rich Oliver关于类型擦除的评论:这主要是编译时间的事情,它们确实是不同的。在第一种情况下,它是一个我们不知道的元素类型的数组。所以我们不能这样做 a(0) = 1,因为它可能是一个很好的 Array[String]。相反,对于第二种类型,它是一种 Array[Any],和(0)= 1是好的。


10
2018-03-29 17:10



不打字擦除不是否定在实践中区分的重要性?我想知道什么时候知道这两种类型的差异真的很有用。 - Rich Oliver
我编辑了我的答案来解决你的观点。 - Didier Dupont
好的一点,我没有想到可变。我将提出第二个问题:区别对于不可变集合/不可变通用对象有什么价值吗? - Rich Oliver
不确定,但我认为它不会对协变结构产生太大影响(这就是我在答案中选择不变“Set”的原因)。实际上,存在类型对于scala来说是一个稍微晚一些的补充,并且主要用于兼容java通用通配符。对于共同/逆转而言,这种做法并不常用。 - Didier Dupont


答案:


不同之处在于类型系统何时决定T是什么。

也许最好的答案是,尝试在某些代码中使用这两种类型,看看会发生什么,并试图找出原因。但我会首先尝试解释一些类型理论。在这种情况下:

Array[T forSome { type T; }]

在哪里 forSome 在里面 [] 括号,每个元素本身只需要以某种方式解释为 T forSome { type T; }。好的,所以 Int 是一个 T 对于某种类型 T,所以你可以放一个 Int 在数组中。一个 String 也是一个 T 对于某种类型 T,因为选择 forSome 你可以说,一次只适用于一个元素 T 是 String 这一次而不是 Int,所以你可以把 String 在数组中,即使它已经包含一个 Int

另一方面,虽然我们可以选择 T 独立于数组的任何元素,已经选择了数组本身的类型:它是可以容纳任何东西的数组类型。它不可能是一个 Array[Int] 或者 Array[String]

但在这种情况下:

Array[T] forSome { type T; }

方式 T 在阵列之外只决定一次。该阵列实际上可能是一个 Array[Int],和 Array[String]或者 Array[Any]。但一旦类型 T 如果选择,则阵列的所有元素必须与该类型一致。

好, 现在 让我们尝试一些代码。这是一个例子:

scala> var a = Array(1,2);
a: Array[Int] = Array(1, 2)

scala> def first(z : Array[T] forSome { type T }) = z(0);
first: (z: Array[_])Any

scala> def firstany(z : Array[T forSome { type T }]) = z(0);
firstany: (z: Array[T forSome { type T }])Any

scala> first(a);
res0: Any = 1

scala> firstany(a);
error: type mismatch;
 found   : Array[Int]
 required: Array[T forSome { type T }]
       firstany(a);
                ^

为什么错误?因为阵列 a 有类型 Array[Int],只能包含类型的东西 Int。那肯定是一个 Array[T] forSome {type T},因为所有类型系统需要做的是选择Int作为类型T.因此 first(a) 没关系但正如上面所解释的那样 Array[T forSome { type T }] 不可能是一个 Array[Int]所以 firstany(a) 是一个错误。

但是以下是可以的,因为一个 Array[Any] 是一个 Array[T forSome { type T }]

scala> var b = Array(1, "x");
b: Array[Any] = Array(1, x)

scala> firstany(b);
res1: Any = 1

6
2018-03-29 18:41





改变括号的位置可能会改变很多意义,这不足为奇。有点像你切换∃和∀

第一个意思是:有一些类型 T 这是一个 Array[T]。所以这很满意 Array[String]Array[Int]等数组是同质的,但我们不知道哪种类型。

第二个意思是:对于数组的每个元素,都有一些类型 T,使元素属于类型 T。这根本没有约束,所以它只是 Array[Any]。有了 forSome 在方括号内,如果你这样做,可能会很有用

Array[Set[T] forSome {type T}]

这意味着数组的元素都是集合,但在同一个数组中可能同时存在一组Int和一组字符串。


关于Rich Oliver关于类型擦除的评论:这主要是编译时间的事情,它们确实是不同的。在第一种情况下,它是一个我们不知道的元素类型的数组。所以我们不能这样做 a(0) = 1,因为它可能是一个很好的 Array[String]。相反,对于第二种类型,它是一种 Array[Any],和(0)= 1是好的。


10
2018-03-29 17:10



不打字擦除不是否定在实践中区分的重要性?我想知道什么时候知道这两种类型的差异真的很有用。 - Rich Oliver
我编辑了我的答案来解决你的观点。 - Didier Dupont
好的一点,我没有想到可变。我将提出第二个问题:区别对于不可变集合/不可变通用对象有什么价值吗? - Rich Oliver
不确定,但我认为它不会对协变结构产生太大影响(这就是我在答案中选择不变“Set”的原因)。实际上,存在类型对于scala来说是一个稍微晚一些的补充,并且主要用于兼容java通用通配符。对于共同/逆转而言,这种做法并不常用。 - Didier Dupont