Re: [問題] Scala 的 Covariant/Contravariant/Inv …
※ 引述《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
討論串 (同標題文章)
以下文章回應了本文 (最舊先):
完整討論串 (本文為第 11 之 17 篇):
PLT 近期熱門文章
PTT數位生活區 即時熱門文章