我们来谈谈差异。
这是基本概念。考虑类型A -> B
。我想让你想象的是,这种类型类似于“拥有一个B
”并且还“欠A
”。事实上,如果你还清你的A
您立即收到您的B
。函数有点像托管。
“拥有”和“拥有”的概念可以扩展到其他类型。比如最简单的容器
newtype Box a = Box a
行为如下:如果你“有”Box a
那么你也“拥有”一个a
。我们考虑具有 kind 的类型* -> *
并且“让”他们的参数成为(协变)函子,我们可以将它们实例化为Functor
instance Functor Box where fmap f (Box a) = Box (f a)
如果我们考虑类型上的谓词类型,会发生什么,例如
newtype Pred a = Pred (a -> Bool)
在这种情况下,如果我们“有”一个Pred a
,我们实际上“欠”了一个a
。这源于a
位于的左侧(->)
箭。在哪里fmap
of Functor
是通过将函数传递到容器中并将其应用到我们“拥有”内部类型的所有位置来定义的,我们不能对Pred a
因为我们没有“拥有”并且a
s.
相反,我们会这样做
class Contravariant f where
contramap :: (a -> b) -> (f b -> f a)
现在contramap
就像“翻转”一样fmap
?它将允许我们将该功能应用到我们“拥有”的地方b
in Pred b
为了收到Pred a
。我们可能会打电话contramap
“以物易物”,因为它编码了这样一个想法:如果你知道如何获得b
s from a
那么你可以将债务b
负债累累a
s.
让我们看看它是如何工作的
instance Contravariant Pred where
contramap f (Pred p) = Pred (\a -> p (f a))
我们只是使用以下方式进行交易f
在将其传递到谓词函数之前。精彩的!
所以现在我们有协变和逆变类型。从技术上讲,它们被称为协变和逆变“函子”。我还将立即声明,逆变函子几乎总是不协变的。因此,这回答了您的问题:存在一堆无法实例化的逆变函子Functor
. Pred
是其中之一。
不过,有一些棘手的类型既是逆变函子又是协变函子。特别是,常数函子:
data Z a = Z -- phantom a!
instance Functor Z where fmap _ Z = Z
instance Contravariant Z where contramap _ Z = Z
事实上,你基本上可以证明任何既是Contravariant
and Functor
有一个幻像参数。
isPhantom :: (Functor f, Contravariant f) => f a -> f b -- coerce?!
isPhantom = contramap (const ()) . fmap (const ()) -- not really...
另一方面,像这样的类型会发生什么
-- from Data.Monoid
newtype Endo a = Endo (a -> a)
In Endo a
我们都欠并收到a
。这是否意味着我们没有债务了?嗯,不,这只是意味着Endo
既想要协变又想要逆变and没有幻像参数。结果:Endo
is 不变的并且不能实例化Functor
nor Contravariant
.