首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >为什么协方差/反方差意味着只读/只写?

为什么协方差/反方差意味着只读/只写?
EN

Stack Overflow用户
提问于 2018-09-18 00:26:12
回答 5查看 902关注 0票数 6

如果您查看接口中的关于协变/反变的流程文档字段,协变量意味着只读,而逆方差意味着只写。但是,我真的不明白为什么。在他们的文档中,它们被定义为

协方差

  • 协方差不接受超级类型。
  • 协方差确实接受子类型。

Contravariance

  • 反方差确实接受超级类型。
  • 反方差不接受子类型。

但在我的脑海中,这并不是真正意义上的只读/只写。有人能更深入地解释一下为什么会这样吗?

EN

回答 5

Stack Overflow用户

回答已采纳

发布于 2018-09-18 17:21:35

我不熟悉这种语言的语法,所以这个答案是用伪代码回答的。

假设我们有三种类型,Siamese < Cat < Animal,并定义了一个接口

代码语言:javascript
复制
interface CatCage {
    cat: Cat
}

写一些方法

代码语言:javascript
复制
get_cat_in_cage (CatCage c) -> Cat {
    c.cat
}

put_cat_in_cage (Cat c, CatCage cage) {
    cage.cat = c
}

协方差

如果我们使字段协变量,我们可以定义一个实例,如

代码语言:javascript
复制
SiameseCage < CatCage {
    cat : Siamese
}

但如果我们这么做

代码语言:javascript
复制
put_cat_in_cage (aCat, aSiameseCage)

在这种情况下,aSiameseCage.cat的价值是什么?SiameseCage认为它应该是一个Siamese,但是我们刚刚能够使它成为一个Cat --很明显,这个字段不能同时写在接口上并且是协变的。

反方差

如果我们将字段设置为反变体,则可以定义如下所示的实例

代码语言:javascript
复制
AnimalCage < CatCage {
    cat : Animal
}

但现在我们不能

代码语言:javascript
复制
get_cat_in_cage (anAnimalCage)

因为anAnimalCage.cat的值不一定是Cat。因此,如果字段是反变体,就无法在接口上读取。

您可以通过返回一个Object或任何基本类型来使它在接口上具有可读性,但这可能没有任何真正的用例,因此该语言在决定不使用它时是明智的。

票数 5
EN

Stack Overflow用户

发布于 2018-09-18 00:53:18

既然你给这个哈斯克尔贴上标签,我可以随便用点Haskell.格拉斯哥大变种人。

代码语言:javascript
复制
{-# 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

代码语言:javascript
复制
readDFromC :: (forall a. c a :- d a) -> IORef (Foo c) -> IO (Foo d)
readDFromC cd ref = upcast cd <$> readIORef ref

类似地,我可以做双翻转,将Foo d替换为Foo c

代码语言:javascript
复制
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

票数 3
EN

Stack Overflow用户

发布于 2018-09-19 01:38:24

反方差只是指“在相反的方向上变化”(而协方差只是指“在同一方向上的变化”)。在子类型关系的上下文中,它指的是复合类型是另一种类型的子类型的情况,如果它的一个部分是另一个类型中相同部分的超型

所谓“复合类型”,我只是指具有其他组件类型的类型。像Haskell、Scala和Java这样的语言通过声明类型具有参数来处理这个问题(Java称之为“泛型”)。简单地看一下到流文档的链接,看起来Flow没有格式化参数,并且有效地将每个属性的类型考虑为一个单独的参数。因此,我将避免细节,只讨论由其他类型组成的类型。

子类型都是关于可替换性的。如果有人想要一个T,我可以给他们一个T的任何子类型的值,没有什么问题;他们“允许”做的事情,他们所要求的事情,只是那些对任何可能的T有效的事情。当类型具有其他类型的子结构时,就会出现差异。如果有人要求一个包含组件类型T的结构的类型,并且我想给他们一个具有相同结构但组件类型是S的类型的值,那么这是有效的吗?

如果组件类型存在,因为它们可以使用所请求的对象获得T值(比如读取属性,或者调用返回T值的方法),那么当我给它们我的值时,它们将从它中获取S值,而不是他们所期望的T值。他们希望使用这些值执行Tish操作,只有当ST的子类型时才能工作。所以对于复合类型,我必须是他们想要的一个子类型,其中一个必须是他们想要的组件的一个子类型。这是协方差

另一方面,如果组件类型存在,因为它们可以向所请求的对象发送T值(比如编写属性,或者调用一个以T值作为参数的方法),那么当我给它们我的值时,它将期望它们发送S值而不是T值。我的对象将希望使用其他人将要发送给它的S值来执行T ish操作。只有当TS的一个子类型时,这才能起作用。所以在这种情况下,对于复合类型,我必须是他们想要的一个子类型,其中一个的分量必须是他们想要的那个组件的一个超级类型。我是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列表。

票数 3
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/52377306

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档