作者godfat (godfat 真常)
看板PLT
標題Re: [問題] Scala 的 Covariant/Contravariant/Inv …
時間Sun Mar 15 15:21:36 2009
※ 引述《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)
1F:推 jaiyalas:推最後的廣告(淚光~) XD 03/17 09:48
2F:推 noctem:(淚....) 03/17 19:17