作者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)