作者macbuntu (邀怪)
看板PLT
标题Re: [问题] Scala 的 Covariant/Contravariant/Inv …
时间Tue Mar 17 21:38:20 2009
※ 引述《sbrhsieh (sbr)》之铭言:
: : interface A<+T> {
: : public T get(); // OK
: : }
: : interface C<-T> {
: : public void set(T t); // OK
: : }
: : interface E<T> {
: : public T get(); // OK
: : public void set(T t); // OK
: : }
: 我认为 generic type 大部分会是上述的 interface E 这种型态居多,这种类型
: 的 generic type 在 scala 里只能定义为 non-variant subtyping,那麽用上此
: feature 的机会不多。
我觉得因为 Java type parameter 是 invariant 的, 所以看不出 A 和 C 的好处.
举个使用 covariant type 的例子 (pseudo java):
interface Set<+T> {
public T get();
}
class Algorithm {
public static Number findMax(Set<Number> s) { .... }
}
// 下面都可以用, 因为 type parameter 是 covariant
Set<Integer> si = /* new ... */;
Integer i = Algorithm.findMax(si);
Set<Double> sd = /* new ... */;
Double d =Algorithm.findMax(sd);
实务上常常会有很多东西可以被 model 成 immutable object, 只能读取不能修改,
如果使用 generics 来设计这些东西, parameterized type 之间能有继承的关系,
譬如可以设计成 IPod 继承 Product 则 Parts<IPod> 继承 Parts<Product>,
我觉得很多时候会蛮直觉的.
: 第二,我认为不应该是由 generic type 来决定 subtyping variance,而是由
: client code 来决定。
: ,,,
: 如果一个 Java programmer 没有对 wildcard/bounded wildcard 有足够的观念,
: 那麽即使把 scala Variance Annotations 加入 Java PL,对他们不会带来多大的
: 好处(因为 Variable 这种类型的 generic type 占大多数)。
这就很难说哪样比较好了... 两种方法虽然表面上看起来都可以做到一样的事,
譬如我上面举的例子您可能说宣告 findMax(Set<? extends Number> a) 就好了,
但这两种方法有一个微妙的差别:
Java 的方法表示宣告 variance 的责任落在使用型别的人,
而 Scala 的方法表示宣告 variance 的责任落在设计型别的人.
我本来以为 Java 的方法比较保守, 但後来仔细看了 Scala 後, 反而觉得
Java 的方法比较乱, 虽然可以活用, 但这样活用的代价是型别会被允许处在一个
语意不正确的状态. 我来举个例子:
interface A
<T> {
public
T get();
public void set(
T t);
}
static void func(A
<? extends Number> a) {
Number n = a.get(); // OK
a.set(
n); // compile time error
a.set(
123); // compile time error
a.set(
new Object()); // compile time error
a.set(
null); // only this is OK
}
上面那个 func() 里面, 如果没有前三个 set() 呼叫, 是可以 compile 没问题的.
但是 T 用在 parameter type 的时候根本不该允许 <? extends Number>
这种 covariant binding, 也就是说, Java compiler 允许 A 处在一个不正确
的状态, 让 A.set() 变成完全无用, 只能放 null 进去. 只允许 s.set(null)
从语意上就变得说不通了, 原先的 A<T> 可以 a.set(a.get()) 为什麽
进到 func() 里後不能 a.set(a.get())? 是因为 A 里的 T 只该允许 invariant.
Scala 的方法让整个 type polymorphism 变得很一致, 即使有 type parameter
还是可以有继承的关系. Type parameter variance 的能力跟本来就 T 在型别里面
使用的位置有直接的关系, 所以宣告 variance 的责任落在设计型别的人身上,
感觉既合理又清楚. 如此一来, 使用型别的人没有办法以错误的方法使用它,
所以上面的例子用 Scala 的方法来写的话, 设计 A 的人当初如果宣告 A<T>,
是 invariant, 则没有变成 covariant/contravariant 的机会:
interface A
<T> { .... }
void func(A
<Number> a) { ... }
A<Integer> i = /* new something */
func(
i);
// compile time error
A<Object> o = /* new something */
func(
o);
// compile time error
A<Number> n = /* new something */
func(
n); // OK
如果需要 covariant 或是 invariant, 当初设计 A 的人就要设计一个是可以允许
+T 或是 -T 的介面, 将来才可以这样用:
interface A
<+T> { .... }
void func(A
<Number> a) { ... }
A<Integer> i = /* new something */
func(
i); // OK
A<Object> o = /* new something */
func(
o);
// compile time error
A<Number> n = /* new something */
func(
n); // OK
在使用型别的地方不需要用 <? extends X> 这种东西, 因为这已经定义在 A 里了,
型别不会像 Java 那样处在不正确的状态. 习惯 Java 的人会说 Java 用法比较灵活,
但我自己觉得 Scala 的 variant 方法是往前更进了一步, 语意也更乾净漂亮.
(哈, 语意而已... Scala 的语法我就很难习惯了... Java 中毒太深 :P )
--
※ 发信站: 批踢踢实业坊(ptt.cc)
◆ From: 60.251.144.115