在类型级别你有另一种编程语言,almost-哈斯克尔。特别是,您可以将类型视为具有构造函数并且能够部分应用。
为了更严格地看待这一点,我们引入了“类型的类型”,称为“种类”。例如,类型构造函数Int
有善良
Int ::: *
我写的地方(:::)
阅读“has kind”,尽管这不是有效的 Haskell 语法。现在我们还有“部分应用类型构造函数”,例如
Maybe ::: * -> *
它的函数类型就像您在值级别所期望的那样。
类型的概念有一个非常重要的概念——只有当值是类型时才可以实例化类型*
。或者,例如,不存在类型的值Maybe
x :: Maybe
x = -- .... what!
事实上,甚至不可能表达除*
我们期望该类型描述值的任何地方。
这导致 Haskell 中“类型级函数”的能力受到某种限制,因为我们不能普遍传递“未应用的类型构造函数”,因为它们并不总是有意义。相反,整个系统的设计使得只能构建合理的类型。
但允许表达这些“高级类型”的地方是类型类定义。
如果我们启用KindSignatures
然后我们就可以直接写我们的类型的种类了。出现这种情况的一个地方是类定义中。这是Show
class Show (a :: *) where
show :: a -> String
...
这是完全自然的,因为该类型的出现a
在方法的签名中Show
是有价值观的。
但当然,正如您在这里指出的,Functor
是不同的。如果我们写出它的签名,我们就会明白为什么
class Functor (f :: * -> *) where
fmap :: (a -> b) -> f a -> f b
这是一种非常新颖的多态性,更高级的多态性,所以你需要花一点时间来理解它。但值得注意的是f
只出现在方法中Functor
正在应用到其他一些类型a
and b
。特别是,像这样的课程会被拒绝
class Nope (f :: * -> *) where
nope :: f -> String
因为我们告诉系统f
有善良(* -> *)
但我们使用它就好像它可以实例化值,就好像它很友善一样*
.
一般情况下我们不需要使用KindSignatures
因为 Haskell 可以直接推断签名。例如,我们可以(事实上确实如此)写
class Functor f where
fmap :: (a -> b) -> f a -> f b
哈斯克尔推断出这种f
必须是(* -> *)
因为它似乎适用于a
and b
。同样,如果我们编写不一致的内容,我们可能会失败“种类检查”,就像我们无法通过类型检查一样。例如
class NopeNope f where
fmap :: f -> f a -> a
暗示f
有善良*
and (* -> *)
这是不一致的。