这个类型让我大吃一惊:
class Contravariant (f :: * -> *) where
contramap :: (a -> b) -> f b -> f a然后我读了this,但与标题相反,我并没有更开明。
有没有人可以解释一下什么是逆变函数式,以及一些例子?
发布于 2016-06-26 19:27:27
首先,@haoformayor的答案非常好,所以考虑一下这更像是一个附录,而不是完整的答案。
定义
我喜欢考虑函数器(协变量/逆变量)的一种方式是从图的角度考虑。该定义反映在以下几个定义中。(我用cmap缩写contramap )
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 = φ x和y ↦ ψ y是有意义的,在cmap的语句中省略的是
c -> a不是φ :: a -> b而是ψ ::
因此,ψ不能接受φ的结果,但它可以将其参数转换为φ可以使用的东西-因此,x ↦ y = ψ x和y ↦ φ y是唯一正确的选择。
这反映在下面的图表中,但在这里,我们已经将具有共同源/目标的函数的例子抽象为具有协变/逆变属性的东西,这是你在数学和/或haskell中经常看到的东西。
covariant
f a ─── fmap φ ───▶ f b ─── fmap ψ ───▶ f c
▲ ▲ ▲
│ │ │
│ │ │
a ─────── φ ──────▶ b ─────── ψ ──────▶ c
contravariant
g a ◀─── cmap φ ─── g b ◀─── cmap ψ ─── g c
▲ ▲ ▲
│ │ │
│ │ │
a ─────── φ ──────▶ b ─────── ψ ──────▶ c备注:
在数学中,你通常需要一个法则来调用函数器。
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 ψ这相当于说
fmap ψ . fmap φ = fmap (ψ.φ)鉴于
cmap φ . cmap ψ = cmap (ψ.φ)发布于 2016-06-26 12:45:43
首先,请注意我们的朋友Functor类。
你可以把Functor f看作是一个断言,a永远不会出现在“否定的位置”。这是该概念的一个深奥术语:请注意,在以下数据类型中,结果似乎充当“a”变量。
newtype IO a = IO (World -> (World, a))newtype Identity a = Identity anewtype 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 constantnewtype 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求值。
相当冗长!但解决新型诱导的冗长问题将是另一个问题……
其他资源(找到后在此处添加链接)
发布于 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.
第一个参数会生成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)).
f b).
contraMap ):第二个参数(即整个粘合在一起的东西:contraMap的最终输出)(一个知道如何使用a的东西,即f a).https://stackoverflow.com/questions/38034077
复制相似问题