作者sbrhsieh (sbr)
看板PLT
标题Re: [问题] Scala 的 Covariant/Contravariant/Inv …
时间Wed Mar 18 16:25:53 2009
※ 引述《macbuntu (邀怪)》之铭言:
: 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.
我现在还是不能理解,为何你会把上述的例子当作是语法上的瑕疵。
我举几个例子:
static void func(A<? extends Number> a) {
Number n = a.get();
a.set(n); // 应该要 OK 比较直觉
}
如果你把文脉考虑进来,会觉得会有编译错误似乎不太对,但是如果你以编译器的
观点,或是 runtime bytecode 层面去看:
static void func(A<? extends Number> a) {
// 姑且不论值从哪里来,VM 在 runtime 只知道 n 的 value 是 Number ref
Number n = ...; // n could hold Integer, Double...etc ref value
a.set(n); // 应该要 OK 吗?
}
如果你还是觉得变数的 n 的确切型别编译器可以从 context deduce 出来,暂时把
例子想成:
static void func(A<? extends Number> a, Number n) {
a.set(n);
}
当你看到 client code:
func(new A<Float>()); //
func(new A<Double>(), new Integer(87));
编译器不放行是对的。
※ 引述《godfat (godfat 真常)》之铭言:
: 我觉得到了这边的话,可能就要回头来看 variance annotation 原本
: 所表达出的语意,也就是 A[+T] 时表示如果 U < T, 则 A[U] < A[T]
: 所以你能在 scala 里做这件事,却不能在 java 里做:
:
: val int_list: List[Integer] = List(1,2,3)
: val any_list: List[Any] = int_list
:
: 虽然 java array 是可以的,因此 java array 本身似乎带有 +T 的意义?
: 也就是说并不只是在 argument 上可以有 variance, value/variable 也是可以的。
formal parameter 本质上就是 local variable,所以在 Java 中也是可以这样做:
java.util.List<Integer> int_list = new java.util.ArrayList<Integer>();
java.util.List<? extends Object> any_list = int_list;
这时候可不能说:可是在 Java case 里,any_list "却" 没有办法放进任何东西。
因为 scala case 中,any_list 一样不能放进任何东西,你说『scala 中的 List
本来就是设计成 immutable』,我会说『就因为 scala List 设计成 immutable
所以你才能有一个 List[+T],让你在 scala 中能以比较短的写法 List[Any]
作跟 Java List<? extends Object>(写法比较长) 一样多的事』。你能够有
covariant subtyping 的 List[+T],是可遇不可求的。
那反过来,如果假设 scala List 当初也是设计成 mutable 容器(也就是 List[T]),
那麽我可以在 Java 里这样子使用 local variable:
java.util.List<Integer> int_list = new java.util.ArrayList<Integer>();
java.util.List<? extends Object> any_list = int_list;
System.out.println(any_list.get(0));
请问,在 scala 中该怎麽作?
========================================================================
一来一往到这一篇,我是觉得有点乱了,或许每个人想要交流的看法并没有真的
有交流到。
Java Generics 最後采用的语法的确是不容易掌握(也就是不直觉),我认为会造成
这样子的原因在於,当初 Java team 进行引入 generics 的一个主要原则:
令 JVM (spec) 必要的修改最小,不是令它的语法既简单又美
以最後实现在 Java 1.5 中的 generics 来说,JVM 规格配合 generics 而作的
修改,真的是蛮小的。主要是增加 type parameter, type bounds 这些资讯
进 class/method bytecode,让 compiler 可以读取到,JVM 的 instruction 没有
任何一个有修改。
最後,我认为每一个 Java programmer,没有看过这两个 paper(除非完全不用到
Java Generics)的人,都应该花时间看看:
Adding Wildcards to the Java Programming Language
http://www.jot.fm/issues/issue_2004_12/article5.pdf
On Access Restriction with Java Wildcards
http://www.jot.fm/issues/issue_2005_12/article6.pdf
--
※ 发信站: 批踢踢实业坊(ptt.cc)
◆ From: 218.173.129.21