首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >什么是逆变函数式?

什么是逆变函数式?
EN

Stack Overflow用户
提问于 2016-06-26 08:11:08
回答 4查看 7.9K关注 0票数 43

这个类型让我大吃一惊:

代码语言:javascript
复制
class Contravariant (f :: * -> *) where
  contramap :: (a -> b) -> f b -> f a

然后我读了this,但与标题相反,我并没有更开明。

有没有人可以解释一下什么是逆变函数式,以及一些例子?

EN

回答 4

Stack Overflow用户

发布于 2016-06-26 19:27:27

首先,@haoformayor的答案非常好,所以考虑一下这更像是一个附录,而不是完整的答案。

定义

我喜欢考虑函数器(协变量/逆变量)的一种方式是从图的角度考虑。该定义反映在以下几个定义中。(我用cmap缩写contramap )

代码语言:javascript
复制
      covariant                           contravariant
f a ─── fmap φ ───▶ f b             g a ◀─── cmap φ ─── g b
 ▲                   ▲               ▲                   ▲
 │                   │               │                   │
 │                   │               │                   │
 a ────── φ ───────▶ b               a ─────── φ ──────▶ b

注意:这两个定义中唯一的变化是顶部的箭头(以及名称,所以我可以将它们称为不同的东西)。

示例

当谈到这些时,我脑海中总是有一个例子是函数-然后一个f的例子是type F a = forall r. r -> a (这意味着第一个参数是任意的但固定的r),或者换句话说,所有具有公共输入的函数。和往常一样,(协变) Functor的实例只有fmap ψ φ =ψ。φ`。

其中(逆变量) Functor是具有公共结果- type G a = forall r. a -> r的所有函数,这里的Contravariant实例将是cmap ψ φ = φ . ψ

但是这到底意味着什么呢?

φ :: a -> bψ :: b -> c

因此,通常(ψ . φ) x = ψ (φ x)x ↦ y = φ xy ↦ ψ y是有意义的,在cmap的语句中省略的是

c -> a不是φ :: a -> b而是ψ ::

因此,ψ不能接受φ的结果,但它可以将其参数转换为φ可以使用的东西-因此,x ↦ y = ψ xy ↦ φ y是唯一正确的选择。

这反映在下面的图表中,但在这里,我们已经将具有共同源/目标的函数的例子抽象为具有协变/逆变属性的东西,这是你在数学和/或haskell中经常看到的东西。

代码语言:javascript
复制
                 covariant
f a ─── fmap φ ───▶ f b ─── fmap ψ ───▶ f c
 ▲                   ▲                   ▲
 │                   │                   │
 │                   │                   │
 a ─────── φ ──────▶ b ─────── ψ ──────▶ c


               contravariant
g a ◀─── cmap φ ─── g b ◀─── cmap ψ ─── g c
 ▲                   ▲                   ▲
 │                   │                   │
 │                   │                   │
 a ─────── φ ──────▶ b ─────── ψ ──────▶ c

备注:

在数学中,你通常需要一个法则来调用函数器。

代码语言:javascript
复制
        covariant
   a                        f a
  │  ╲                     │    ╲
φ │   ╲ ψ.φ   ══▷   fmap φ │     ╲ fmap (ψ.φ)
  ▼    ◀                   ▼      ◀  
  b ──▶ c                f b ────▶ f c
    ψ                       fmap ψ

       contravariant
   a                        f a
  │  ╲                     ▲    ▶
φ │   ╲ ψ.φ   ══▷   cmap φ │     ╲ cmap (ψ.φ)
  ▼    ◀                   │      ╲  
  b ──▶ c                f b ◀─── f c
    ψ                       cmap ψ

这相当于说

代码语言:javascript
复制
fmap ψ . fmap φ = fmap (ψ.φ)

鉴于

代码语言:javascript
复制
cmap φ . cmap ψ = cmap (ψ.φ)
票数 17
EN

Stack Overflow用户

发布于 2016-06-26 12:45:43

首先,请注意我们的朋友Functor类。

你可以把Functor f看作是一个断言,a永远不会出现在“否定的位置”。这是该概念的一个深奥术语:请注意,在以下数据类型中,结果似乎充当“a”变量。

  • newtype IO a = IO (World -> (World, a))
  • newtype Identity a = Identity a
  • newtype List a = List (forall r. r -> (a -> List a -> r) -> r)

在这些例子中,a都是正面的。在某种意义上,每种类型的a代表函数的“结果”。将第二个示例中的a视为() -> a可能会有所帮助。记住第三个示例等同于data List a = Nil | Cons a (List a)可能会有所帮助。在像a -> List -> r这样的回调中,a出现在负值位置,但回调本身却处于负值位置,所以负值和负值相乘成正值。

用于对函数参数进行签名的方案是elaborated in this wonderful blog post

现在请注意,这些类型中的每一个都接受一个Functor。这不是错误!函数器旨在对类别协变函数器的概念进行建模,该函数器“保持箭头的顺序”,即f a -> f b而不是f b -> f a。在Haskell中,a从不出现在负值位置的类型总是允许Functor。我们说这些类型在a上是协变的。

换句话说,可以将Functor类有效地重命名为Covariant。他们是同一个想法。

这个想法之所以用"never“这个词表达得如此奇怪,是因为a既可以出现在正位置,也可以出现在负位置,这种情况下,我们说类型在a上是不变的。双变量也可能永远不会出现(比如幻影类型),在这种情况下,我们说该类型在a - a上既是协变的,也是逆变的。

回到对立面

因此,对于a从未出现在正位置的类型,我们说它在a中是逆变量。每种类型的Foo a都会接受一个instance Contravariant Foo。以下是取自contravariant包的一些示例:

  • data Void a(a为phantom)
  • data Unit a = Unit (a为幻影again)
  • newtype Const constant a = Const constant
  • newtype WriteOnlyStateVariable a = WriteOnlyStateVariable (a -> IO ())
  • newtype Predicate a = Predicate (a -> Bool)
  • newtype Equivalence a = Equivalence (a -> a -> Bool)

在这些示例中,a要么是双变量,要么仅仅是逆变量。a要么从未出现,要么为负值(在这些人为设计的示例中,a总是出现在箭头之前,因此确定这一点非常简单)。因此,这些类型中的每一个都接受一个instance Contravariant

一个更直观的练习是斜视这些类型(表现出逆变),然后斜视上面的类型(表现出协变),看看你是否能直观地判断出a语义上的差异。也许这是有帮助的,或者也许它仍然是深奥的花招。

这些在什么时候可能是实际有用的?例如,让我们根据cookie的芯片类型对cookie列表进行分区。我们有一个chipEquality :: Chip -> Chip -> Bool。要获得Cookie -> Cookie -> Bool,我们只需对runEquivalence . contramap cookie2chip . Equivalence $ chipEquality求值。

相当冗长!但解决新型诱导的冗长问题将是另一个问题……

其他资源(找到后在此处添加链接)

票数 15
EN

Stack Overflow用户

发布于 2019-05-15 21:05:49

我知道这个答案不会像其他答案那样深奥,但它只是基于你会遇到的逆变量的常见实现。

首先,提示:在阅读contraMap函数类型时,不要使用与阅读好的函数类型的map时相同的对f的心理隐喻。

你知道你是怎么想的:

“包含(或产生)t的东西”

你读一个像f t这样的类型

在这种情况下,你不能再这么做了。

逆变函数器是经典函数器的“对偶”,所以,当你在contraMap中看到f a时,你应该想到“对偶”的比喻:

f t是一个消耗t的东西

现在contraMap的类型应该开始有意义了:

contraMap :: (a -> b) -> f b ...

...pause就在那里,类型是非常合理的:

“产生”“消费”b.的b.

  • A对象的函数

第一个参数会生成b。第二个参数吃掉了b

很有道理,对吧?

现在完成类型的编写:

contraMap :: (a -> b) -> f b -> f a

因此,最终这件事必须产生一个“a的消费者”。

当然,我们可以构建它,因为我们的第一个参数是一个接受a作为输入的函数。

对于构建“a的消费者”,函数(a -> b)应该是一个很好的构建块。

所以contraMap基本上可以让你创建一个新的“消费者”,就像这样(警告:虚构的符号传入):

(takes a as input / produces b as output) ~~> (consumer of b)

我虚构的符号左边的第一个参数:右边的第一个参数(即(a -> b)).

  • On f b).

  • The contraMap ):第二个参数(即整个粘合在一起的东西:contraMap的最终输出)(一个知道如何使用a的东西,即f a).
票数 11
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/38034077

复制
相关文章

相似问题

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