Re: [問題] Scala 的 Covariant/Contravariant/Inv …

看板PLT (程式語言與理論)作者 (sbr)時間15年前 (2009/03/18 01:23), 編輯推噓0(000)
留言0則, 0人參與, 最新討論串7/17 (看更多)
※ 引述《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)
文章代碼(AID): #19lzo3l1 (PLT)
討論串 (同標題文章)
文章代碼(AID): #19lzo3l1 (PLT)