在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)
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?
这个概念被称为逆变函子,在 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 中,最终类型参数通常会被特殊处理。
| 归档时间: |
|
| 查看次数: |
133 次 |
| 最近记录: |