作者sbrhsieh (sbr)
看板PLT
标题Re: [问题] Scala 的 Covariant/Contravariant/Inv …
时间Wed Mar 18 01:23:12 2009
※ 引述《macbuntu (邀怪)》之铭言:
: : 第二,我认为不应该是由 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.
我觉得你没有看懂我的意思,你这样子的说法有点儿倒因为果的味道(请不要认为
这是不友善的用词)。
我说应该由 client code 来决定 subtyping variance,意思是说当上头的 func 因为
它只需要 a.get() 能够获得(至少是) Number instance 时(根本不会用到 A::set
method),设计者就可以把 formal parater a 宣告为 A<? extends Number> type。
你不能反过来说,A<? extends Number> 出现在这个情况下,反而把 a 变成无法使用
set method,你懂我的意思吗?
如果上述的 func 的实作是包括有 compile error 那几个 statement,那麽你就只能
把 formal parameter 宣告为 A<Number>,因为你需要它至少能产生 Number instance
又要至多能 consume/handle Number instance,这两者的交集只有 A<Number>
这种 instance。
如果你把这个例子改成 scala 来作,你会发现你在实作上会跟 Java 几乎一样,
你没有其他的选择。
: 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
Java generics 是透过编译器在编译期的检查来作出 type 上的限制,并没有
runtime 上的强制性,scala 建构在 JVM 上,其 generic type 的设计跟 Java 相似
也是在编译期检查,建议尽量不要往限制 client code's capability 的方向去思考
Java/scala generics 的设计。
: 如果需要 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 )
这就回到我前一篇的提到的第一点,实际上可以去设计出 A<+T> A<-T> 的比例有
多少?
另外,试想如果 interface A<+T> 的多个 client code 类似於
static void func(A<Number> a) {
Number value = a.get();
System.out.println(value.doubleValue());
}
设计者希望 func 能够处理 A<Integer>, A<Double> 等等 instance,由於
interface A 是个 A<+T>,於是就把 a 宣告为 A<Number>,此时行的通。
(这个 scenario 就是你前一篇一开头提到的好处)
有一天 interface A 的设计者替 A 增加了一个操作:
public void set(T t);
导致 A 只能够修改为 A<T>,这时候那些 client code 就必须跟着改,但是依照
client code 使用 A<Number> instance 的方式(pure covarinat),应该是即使
interface A 变成 non-variant,还是要能正确处理 A<Integer>, A<Double> 等等
的 instance。那麽为了不被 interface 变更的影响,是不是一开始我就把 func
定义成
static void func(A<
? extends Number> a) {
Number value = a.get();
System.out.println(value.doubleValue());
}
还比较好,反正我使用 a 的方式就是我只要 a.get() 至少是得到 Number 就好,我
不在乎 interface A 到底是设计成 covariant, contravariant 还是 non-variant
subtyping。
反过来说,如果 func 使用 formal parameter a 的方式就是 a.get() 至少要是
Number instance,同时至多会丢进 Number instance 给 a.set method,那麽不管
是 Java 还是 scala,你的做法会是一样的(只能把 a 宣告为 A<Number>)。
所以我不觉得 scala 有 Variance Annotation 是好事。
--
※ 发信站: 批踢踢实业坊(ptt.cc)
◆ From: 218.173.129.21
※ 编辑: sbrhsieh 来自: 218.173.129.21 (03/18 02:07)
※ 编辑: sbrhsieh 来自: 218.173.129.21 (03/18 19:12)