在Haskell中,类型构造函数当然可以采用类型参数。
函数a -> b
,当被视为“具有有趣构造函数名称的类型”时,类型为(->) a b
。这使它成为一个带有两个参数(->)
和a
的类型构造函数b
。这在“阅读器”模式中经常遇到,如Functor
和Applicative
个实例:
instance Functor ((->) a) where
fmap = (.)
instance Applicative ((->) a) where
pure = const
(<*>) f g x = f x (g x)
当我第一次尝试理解这个实例的用法时,就像在
fmap (+1) (*2) 3
(=== (+1) . (*2) $ 3
=== 3*2+1
=== 7
)
我的反应是“好的,(+1)
的类型为Int -> Int
,(->) Int Int
,因此匹配Functor
....但其中是Int
吗?我通过调用Maybe Int
来制作Just 1
,但我不会通过向(->) Int Int
应用任何内容来制作Int
。事实上,我通过将((->) Int Int)
应用于Int
来销毁!(是的,有Nothing
,但这似乎......堕落。)“
这一切(当然),只要我记得因为一个类型是从构造函数+参数构建的,这并不意味着它的值是从相应类型的构造函数+参数构建的。一些最有趣和最强大(也很难理解)的类型构造函数就是这样((->)
,Lens
,Arrow
等等。
(好吧,真的是Num a => a
,而不是Int
,但让我们忽略它,不相关)
是否有这个概念的名称?考虑类型构造函数的适当心理模型是什么,而不依赖于误导和剥夺权力的拐杖解释“Foo a
是一个结构{{ 1}} 包含类型Foo
)的值?
答案 0 :(得分:4)
这个概念被称为逆变函子,在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
类型有用的直觉是它们能够消耗逆变量参数的零个,一个或多个值,而不是像我们经常想的那样包含协变参数的零个,一个或多个值在考虑Functor
时。
合并这两个概念的是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
类型的构造函数生成的一些填充值a
,通常不成立。特别是我们有这样的协变类型,但也有反变量类型,它们通常是以某种方式消耗其参数化类型变量值的过程,或者更奇特的类型,如完全忽略其类型变量的幻像类型
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
和...“没什么特别的”类型变量,它们不具有任何性质,通常是因为它们被用作协变和逆变类型。
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中,最终的类型参数通常是专门处理的。