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

看板PLT (程式語言與理論)作者 (godfat 真常)時間15年前 (2009/03/15 15:21), 編輯推噓2(200)
留言2則, 2人參與, 最新討論串2/17 (看更多)
※ 引述《macbuntu (邀怪)》之銘言: : 但是仔細想想, 就開始懷疑某些 variance 在實務上的實用性, : 甚至有時候會干擾原先 OO 的語意? 就像你說的, "High explosives, handle with care." 用得好的話,應該是不會的。不是也有一句話, 你沒辦法阻止別人要開槍打在自己的腳上? XD : 譬如從 Covariant 開始, 如果定義 List[+T], : 則只要 A 是 subclass of B (這裡用 A < B 表示), 則 List[A] < List[B]. : 從 OO 的觀點, 這表示 List[A] 可以被當成 List[B] 用在所有 List[B] 出現的地方. : 但實際上卻不然, List[B].add(B) 就不該能用 List[A].add(B) 才對. 在這邊引用 Java 的 array 的例子,應該就算是一個不良應用吧,哈哈 XD 在 Scala 裡,Array[A] 是 invariant 的,不是 covariant 的。 http://www.scala-lang.org/docu/files/api/scala/Array.html 然而 List[+A] 是 covariant, 但會有這個問題嗎?答案應該是不會。 因為 List 其實是 constant, 根本就沒有 add 這種會改變 state 的操作。 因此任你隨意去 point/refer, 不會使得容器被存入錯誤的東西。 也就是說以下: : Java 的 array 就是 covariant, 變得會允許下面這種不正當的操作: : String[] s = new String[5]; : Object[] o = s; // array is covariant so this is allowed : o[3] = new Object(); ^^^^^^ 這件事本身是沒辦法發生在 Scala 的 List 上, 而 Array[A] 則是 invaraint 的。 : String name = s[3]; // throws ArrayStoreException ! 所以絕不會產生這種,我覺得有點可笑的 exception... : 反過來, Contravariant 就更令我納悶了, 如果定義 List[-T], : 表示當 B < A, 則 List[A] < List[B]... that makes my head spin... : 所以如果用 Java 的 pseudo code 來表示, 變成: : Object[] o = new Object[5]; : String[] s = o; // this is allowed if contravariant : 真的會有好的理由在實務上需要這樣的 variant 嗎? 在我自己的感覺, : 這種功能造成的問題可能比解決的問題還多... 用 Java 的角度去看,我想確實是很難找到需要 contravariant 的時候。 然而可以看這裡: http://en.wikipedia.org/wiki/Covariance_and_contravariance_(computer_science) 我們知道 overriding method 可以有 covariant return type, 但是 parameter type 呢?事實上卻是相反,是 contravariant 的。 用同樣的概念,class pointer/reference 可以指向 subclass instance, 所以 parameter 應該也可以用 class pointer/reference 指向 subclass instance. 以 Java 為例的話:(例子隨便舉的啦 @@) class Human{ public Human touch(Mutant m){ return m; } } class Mutant extends Human{ @Override public Mutant touch(Human h){ // ^ OK, covariant return type // ^ OK, contravariant parameter type. return this; } } 不過這只是個美好的夢,Java 不支援的樣子 XD C++ 也不支援,甚至 Scala 自己也不支援... 這邊可以想像成,在 Mutant#touch 裡面,用 Human h 去接 Human#touch 裡的 Mutant m, 應該不會產生什麼不良的操作。 但是 Scala 自己也不支援,或許是因為怕產生一些混亂吧。 可以改看 Function 的例子,那就真的是有 contravariant parameter type 了。 http://www.scala-lang.org/docu/files/api/scala/Function1.html trait Function1[-T1, +R] extends AnyRef 其中 -T1 是第一個 parameter, +R 是其 return type. 一樣的 Human/Mutant 例子: class Human; class Mutant extends Human; // 定義變種,把正常人類轉換成變種人。 // apply 是 mutate 的 method, 當我們寫 mutate(h) 時會喚起。 val mutate = new Function1[Human, Mutant]{ def apply(h: Human) = new Mutant; } // 定義治療,可以把變種人類轉換回正常人類。 val false_heal: Function1[Mutant, Human] = mutate // 定義我,希望可以從變種人轉換回正常人類。 val me: Human = false_heal(new Mutant) 這邊實際上 argument 的套用,就是把 new mutant 丟給 apply 的 h, 而 me 雖然是 Human, 但可以指向 Mutant. 可以想像成 +A 是允許 type 往上層移動,而 -A 則是允許 type 往下層移動。 因此這邊也是可以寫: val create: Function1[Null, Any] = mutate val any: Any = create(null) 這就是極端的例子,丟最底層的 null 進去,而拿到一個最上層的 any 回來。 在 Java 裡,就像是 Object any = mutate(null); 這樣。 : 所以湊在一起就是會有衝突的地方吧? 感覺上 Java 比較保守, : 盡量維持 class based typing 的一致性, 而 Scala 就蠻大膽的, : 全部放進去看看大家怎麼用 (functional programming? anyone? ...) Java 確實是非常保守,實在不是我能夠喜歡的語言,哈哈 XD 而 Scala 也確實是放了一大堆東西進去,多到我覺得有點不可思議... 這一兩天為了寫這幾篇,翻了點 Scala 的東西,開始覺得還滿有趣的 :p 只是他的語法實在有點多變,keyword 也一大堆,要適應可能要一點時間。 至於 functional programming 嘛,看到現在還是覺得 Haskell 最乾淨漂亮。 歡迎來這裡逛逛: http://flolac.iis.sinica.edu.tw/lambdawan/ 雖然說冷了很久很久了(PLT 板也是),不過 function programming 是 最熱門的話題喲。 http://flolac.iis.sinica.edu.tw/lambdawan/forum/22 -- Nobody can take anything away from him. Nor can anyone give anything to him. What came from the sea, has returned to the sea. Chrono Cross -- ※ 發信站: 批踢踢實業坊(ptt.cc) ◆ From: 220.135.28.18 ※ 編輯: godfat 來自: 220.135.28.18 (03/15 15:25)

03/17 09:48, , 1F
推最後的廣告(淚光~) XD
03/17 09:48, 1F

03/17 19:17, , 2F
(淚....)
03/17 19:17, 2F
文章代碼(AID): #19lAo3ED (PLT)
討論串 (同標題文章)
文章代碼(AID): #19lAo3ED (PLT)