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