大多数情况下,人们don't use Invariant
。您想要这样做的原因是,如果您正在使用其中变量同时出现在协变和逆变位置的类型。
newtype Endo a = Endo {appEndo :: a -> a}
newtype Foo a = Foo (Maybe a -> IO a)
data Bar a = Bar [a] (a -> Bool)
这些都不是实例Functor
or Contravariant
,但它们都可以是Invariant
.
人们很少打扰的原因是,如果您需要对这种类型进行大量映射,通常最好将其分解为协变和逆变部分。每个不变函子都可以表示为Profunctor
:
newtype FooP x y = FooP (Maybe x -> IO y)
data BarP x y = Bar [y] (x -> Bool)
Now
Endo a ~= (->) a a
Foo a ~= FooP a a
Bar a ~= BarP a a
-- So we'd likely write newtype Bar a = Bar (BarP a a)
如果您打开包装,通常会更容易看到发生了什么newtype
, dimap
超过底层的Profunctor
,然后再把它包起来,而不是乱搞invmap
.
我们怎样才能改变一个Invariant
函子变成Profunctor
?首先,让我们处理总和和乘积。如果我们可以转身f
and g
转化为函子fp
and gp
,那么我们肯定可以转f :+: g
and f :*: g
转换成等价的函子和和乘积。
那么作曲呢?这有点棘手,但不多。假设我们可以转f
and g
转化为函子fp
and gp
。现在定义
-- Compose f g a ~= ComposeP fp gp a a
newtype ComposeP p q a b = ComposeP (p (q b a) (q a b))
instance (Profunctor p, Profunctor q) => Profunctor (ComposeP p q) where
dimap f g (ComposeP p) = ComposeP $ dimap (dimap g f) (dimap f g) p
现在假设你有一个函数类型;f a -> g a
。这看起来像fp b a -> gp a b
.
我认为这应该涵盖大多数有趣的案例。