在Haskell中,不需要使用该类型的数据构造“使用类型参数构造的类型”的实例

时间:2014-01-06 02:39:51

标签: haskell types

在Haskell中,类型构造函数当然可以采用类型参数。

函数a -> b,当被视为“具有有趣构造函数名称的类型”时,类型为(->) a b。这使它成为一个带有两个参数(->)a的类型构造函数b。这在“阅读器”模式中经常遇到,如FunctorApplicative个实例:

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,但这似乎......堕落。)“

这一切(当然),只要我记得因为一个类型是从构造函数+参数构建的,这并不意味着它的值是从相应类型的构造函数+参数构建的。一些最有趣和最强大(也很难理解)的类型构造函数就是这样((->)LensArrow等等。

(好吧,真的是Num a => a,而不是Int,但让我们忽略它,不相关)

是否有这个概念的名称?考虑类型构造函数的适当心理模型是什么,而不依赖于误导和剥夺权力的拐杖解释“Foo a是一个结构{{ 1}} 包含类型Foo)的值?

1 个答案:

答案 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中,最终的类型参数通常是专门处理的。