我不熟悉这种语言的语法,因此这个答案是伪代码。
假设我们有三种类型:暹罗猫 < 猫 < 动物
,并定义一个接口。
interface CatCage {
cat: Cat
}
编写一些方法
get_cat_in_cage (CatCage c) -> Cat {
c.cat
}
put_cat_in_cage (Cat c, CatCage cage) {
cage.cat = c
}
如果我们将这个领域变成协变的,我们可以定义一个实例,例如:
SiameseCage < CatCage {
cat : Siamese
}
但如果我们这样做
put_cat_in_cage (aCat, aSiameseCage)
aSiameseCage.cat
的值是什么?SiameseCage
认为它应该是一个Siamese
,但我们只能使它成为一个Cat
- 显然,该字段不能在接口上可写并且同时具有协变性。AnimalCage < CatCage {
cat : Animal
}
但现在我们无法做到
get_cat_in_cage (anAnimalCage)
由于无法保证anAnimalCage.cat
的值是一个Cat
,所以如果它是协变的,该字段就不能在接口上可读。
您可以通过返回一个Object
或基本类型来使其在接口上可读,但这可能没有任何实际用例,因此语言在决定不这样做时是明智的。
既然你标记了这个haskell, 我就可以放心地使用一些Glasgow扩展的Haskell了。
{-# language GADTs, ConstraintKinds
, TypeOperators, ScopedTypeVariables, RankNTypes #-}
import Data.Constraint
import Data.Kind
data Foo :: (Type -> Constraint) -> Type where
Foo :: forall a. c a => a -> Foo c
upcast :: forall c d. (forall a. c a :- d a) -> Foo c -> Foo d
upcast cd (Foo (a :: a))
| Sub Dict <- cd :: c a :- d a
= Foo a
IORef (Foo c)
。我可以轻松地从中 读取 一个 Foo d
:readDFromC :: (forall a. c a :- d a) -> IORef (Foo c) -> IO (Foo d)
readDFromC cd ref = upcast cd <$> readIORef ref
同样地,我可以进行双重翻转,将 Foo d
替换为 Foo c
:
writeCToD :: (forall a. c a :- d a) -> (Foo d -> Foo c) -> IORef (Foo d) -> IO ()
writeCToD cd f ref = modifyIORef ref (upcast cd . f)
但是,如果你尝试单独翻转,你会陷入困境,因为无法从d
推导出c
。
T
,我可以给他们任何T
的子类型的值,什么也不会出错;他们能够使用所请求的内容进行的“允许”操作仅限于可能适用于任何可能的T
的操作。当类型具有其他类型的子结构时,则会出现逆变性。如果有人请求一个包含组件类型T
的结构类型,并且我想给他们一个具有相同结构但组件类型是S
的值,那么什么时候合法呢?T
值(例如读取属性或调用返回T
值的方法),则当我给他们我的值时,他们将从中获取S
值而不是他们期望的T
值。他们想要对这些值进行T
ish操作,这只有在S
是T
的子类型时才能正常工作。因此,对于复合类型,我必须是所需类型的子类型,我具有的部分必须是所需类型中的部分的子类型。这就是协变性。Animal
和子类型Dog
和Cat
。Animal animal = new Cat();
AnimalFood food = new DogFood();
AnimalFur fur = animal.GetFur(); // ALLOWED: Both dog fur and cat fur are always animal fur.
animal.Feed(food); // NOT ALLOWED: There are some dog foods cats can't eat.