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

mis*_*bee 5 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)
Run Code Online (Sandbox Code Playgroud)

当我第一次尝试理解这个实例的用法时,如 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是一个Foo 包含类型值的结构" a

J. *_*son 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
Run Code Online (Sandbox Code Playgroud)

更一般地,我们可以将类型中的类型变量视为具有逆变或协变性质(最简单的情况)。例如,默认情况下我们有

newtype Reader t a = Reader (t -> a)

instance Functor (Reader t) where
  fmap ab (Reader ta) = Reader (ab . ta)
Run Code Online (Sandbox Code Playgroud)

这表明第二个类型参数 toReader是协变的,而如果我们颠倒顺序

newtype RevReader a t = RevReader (t -> a)

instance Contravariant (RevReader a) where
  contramap st (RevReader ta) = RevReader (ta . st)
Run Code Online (Sandbox Code Playgroud)

类型的一个有用的直觉Contravariant是它们能够消耗零个、一个或多个逆变参数值,而不是像我们在考虑 s 时经常想到的那样包含零个、一个或多个协变参数值Functor

将这两个概念结合起来就是Profunctor

class Profunctor p where
  dimap :: (a -> b) -> (c -> d) -> p b c -> p a d
Run Code Online (Sandbox Code Playgroud)

正如我们注意到的,它要求第一个类型参数是逆变p* -> * -> *,第二个类型参数是协变的。这个类很好地表征了(->)类型构造函数

instance Profuntor (->) where
  dimap f g h = g . h . f
Run Code Online (Sandbox Code Playgroud)

同样,如果我们认为逆变类型参数是被消耗的,而协变类型参数是被生成的,那么这非常符合关于类型的典型直觉(->)

逆变参数包括的更多类型示例Relation

newtype Relation t = Relation (t -> t -> Bool)

instance Contravariant Relation where
  contramap g (Relation pred) = Relation $ \a b -> pred (g a) (g b)
Run Code Online (Sandbox Code Playgroud)

或者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))
Run Code Online (Sandbox Code Playgroud)

我们看到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
Run Code Online (Sandbox Code Playgroud)

并且......“没有什么特别的”类型变量不能具有任何性质,通常是因为它们被用作协变逆变类型。

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
Run Code Online (Sandbox Code Playgroud)

最后,采用多个参数的类型构造函数的每个参数可能具有不同的性质。不过,在 Haskell 中,最终类型参数通常会被特殊处理。