如果您查看接口中的关于协变/反变的流程文档字段,协变量意味着只读,而逆方差意味着只写。但是,我真的不明白为什么。在他们的文档中,它们被定义为
协方差
Contravariance
但在我的脑海中,这并不是真正意义上的只读/只写。有人能更深入地解释一下为什么会这样吗?
发布于 2018-09-18 17:21:35
我不熟悉这种语言的语法,所以这个答案是用伪代码回答的。
假设我们有三种类型,Siamese < Cat < Animal,并定义了一个接口
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或任何基本类型来使它在接口上具有可读性,但这可能没有任何真正的用例,因此该语言在决定不使用它时是明智的。
发布于 2018-09-18 00:53:18
既然你给这个哈斯克尔贴上标签,我可以随便用点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)但是,如果您尝试单个翻转,您就会陷入困境,因为无法从c派生d。
发布于 2018-09-19 01:38:24
反方差只是指“在相反的方向上变化”(而协方差只是指“在同一方向上的变化”)。在子类型关系的上下文中,它指的是复合类型是另一种类型的子类型的情况,如果它的一个部分是另一个类型中相同部分的超型。
所谓“复合类型”,我只是指具有其他组件类型的类型。像Haskell、Scala和Java这样的语言通过声明类型具有参数来处理这个问题(Java称之为“泛型”)。简单地看一下到流文档的链接,看起来Flow没有格式化参数,并且有效地将每个属性的类型考虑为一个单独的参数。因此,我将避免细节,只讨论由其他类型组成的类型。
子类型都是关于可替换性的。如果有人想要一个T,我可以给他们一个T的任何子类型的值,没有什么问题;他们“允许”做的事情,他们所要求的事情,只是那些对任何可能的T有效的事情。当类型具有其他类型的子结构时,就会出现差异。如果有人要求一个包含组件类型T的结构的类型,并且我想给他们一个具有相同结构但组件类型是S的类型的值,那么这是有效的吗?
如果组件类型存在,因为它们可以使用所请求的对象获得T值(比如读取属性,或者调用返回T值的方法),那么当我给它们我的值时,它们将从它中获取S值,而不是他们所期望的T值。他们希望使用这些值执行Tish操作,只有当S是T的子类型时才能工作。所以对于复合类型,我必须是他们想要的一个子类型,其中一个必须是他们想要的组件的一个子类型。这是协方差。
另一方面,如果组件类型存在,因为它们可以向所请求的对象发送T值(比如编写属性,或者调用一个以T值作为参数的方法),那么当我给它们我的值时,它将期望它们发送S值而不是T值。我的对象将希望使用其他人将要发送给它的S值来执行T ish操作。只有当T是S的一个子类型时,这才能起作用。所以在这种情况下,对于复合类型,我必须是他们想要的一个子类型,其中一个的分量必须是他们想要的那个组件的一个超级类型。我是contravariance.
简单的函数类型是一个很容易理解的具体例子。用Haskell表示法编写的函数类型类似于ArgumentType -> ResultType;它本身是一个具有两个组件类型的复合类型,因此我们可以问一种函数类型是否可以替代另一种函数类型。
假设我有一个Dog值列表,我需要在它之上映射一个函数,以便将它转换为一个Cat值列表。因此,执行映射的函数期望我给它一个类型为Dog -> Cat的函数。
我能给它一个GreyHound -> Cat类型的函数吗?否;映射函数将对列表中的所有Dog值调用我的函数,而且我们不知道它们都是GreyHound值。
我能给它一个Mammal -> Cat类型的函数吗?是的;我的函数只能做对任何Mammal都有效的事情,这显然包括将调用它的列表中的所有Dog值。
我能给它一个Dog -> Siamese类型的函数吗?是的;映射函数将使用此函数返回的Siamese值来构建Cat列表,而Siamese值是Cat值。
我能给它一个Dog -> Mammal类型的函数吗?否;此函数可能将Dog转换为Whale,这将不适合映射函数需要构建的Cat列表。
https://stackoverflow.com/questions/52377306
复制相似问题