这个概念被称为逆变函子,在 Haskell 中是 Contravariant 类型。
class Contravariant f where
contramap :: (b -> a) -> f a -> f b
-- compare
class Functor f where
fmap :: (a -> b) -> f a -> f b
更一般地说,我们可以将类型中的类型变量视为具有逆变或协变性质(最简单的)。例如,默认情况下我们有
newtype Reader t a = Reader (t -> a)
instance Functor (Reader t) where
fmap ab (Reader ta) = Reader (ab . ta)
这表明Reader的第二个类型参数是协变的,而如果我们颠倒顺序
newtype RevReader a t = RevReader (t -> a)
instance Contravariant (RevReader a) where
contramap st (RevReader ta) = RevReader (ta . st)
Contravariant 类型的一个有用直觉是它们能够使用零、一个或多个逆变参数值,而不是像我们经常想到的那样包含零、一个或多个协变参数值考虑Functors。
结合这两个概念的是Profunctor
class Profunctor p where
dimap :: (a -> b) -> (c -> d) -> p b c -> p a d
正如我们所注意到的,它要求p 类似于* -> * -> *,其中第一个类型参数是逆变的,第二个是协变的。这个类很好地描述了(->) 类型构造函数
instance Profuntor (->) where
dimap f g h = g . h . f
同样,如果我们将逆变类型参数视为被消耗,而将协变类型参数视为产生,这完全符合围绕 (->) 类型的典型直觉。
逆变参数类型的更多示例包括Relation
newtype Relation t = Relation (t -> t -> Bool)
instance Contravariant Relation where
contramap g (Relation pred) = Relation $ \a b -> pred (g a) (g b)
或Fold 表示左折叠作为数据类型
newtype Fold a b = Fold b (a -> Fold a b)
instance Profunctor Fold where
dimap f g (Fold b go) = Fold (g b) (go . f)
sumF :: Num a => Fold a a
sumF = go 0 where
go n = Fold n (\i -> go (n + i))
对于Fold a b,我们看到它消耗任意数量的a 类型来生成一个b 类型。
通常我们发现,虽然我们经常有协变和“容器”(严格肯定的)类型,其中某些类型的值 c a 是从类型为 a -> c a 的构造函数和一些填充值 @ 987654345@,一般来说不成立。特别是我们有像这样的协变类型,但也有逆变类型,它们通常是以某种方式消耗其参数化类型变量的值的过程,或者甚至更奇特的类型,例如完全忽略其类型变量的幻像类型
newtype Proxy a = Proxy -- need no `a`, produce no `a`
-- we have both this instance
instance Functor Proxy where
fmap _ Proxy = Proxy
-- and this one, though both instances ignore the passed function
instance Contravariant Proxy where
contramap _ Proxy = Proxy
and...“没什么特别的”类型变量,不能具有任何性质,通常是因为它们被用作协变 和 逆变类型。
data Endo a = Endo (a -> a)
-- no instance Functor Endo or Contravariant Endo, it needs to treat
-- the input `a` differently from the output `a` such as in
--
-- instance Profunctor (->) where
最后,接受多个参数的类型构造函数可能对每个参数有不同的性质。不过,在 Haskell 中,最终类型参数通常会被特殊处理。