Haskell:Kind
在之前的文章里,我们学习了Functor class,并且知道了Functor里面定义了fmap。
Functor class和fmap的定义是这样的:
Prelude> :info Functor
class Functor (f :: * -> *) where
fmap :: (a -> b) -> f a -> f b
上节课我们并没有深入上面定义中的一些细节,比如:
f :: * -> *
这个是什么意思?以及:
f a -> f b
又是什么意思?
在这篇文章里,我们要弄清楚这个知识点。
首先我们知道,数据类型可以带参数,比如之前学的Maybe类型:
Prelude> :info Maybe
data Maybe a = Nothing | Just a -- Defined in ‘GHC.Base’
这里面a就可以后续指代一个具体的类型,通过Just constructor创建具体的数据类型时指定:
Prelude> j = Just 3 :: Maybe Integer
Prelude> :t j
j :: Maybe Integer
我们也可以不指定具体的类型:
Prelude> x = Just
Prelude> :t x
x :: a -> Maybe a
如上所示,我们创建一个x,使用Just constructor,但是没指定具体的类型,所以x的类型是Maybe a,a保持这种类型参数的状态。
但是不管指不指定具体类型,我们的数据的类型不会是Maybe本身。要么是Maybe a,要么a是具体的类型比如上面的Maybe Integer。
那么Maybe本身怎么去表示呢?是不是不存在Maybe,而只存在Maybe a作为一个整体?
其实我们稍加思考就知道,Haskell在语言实现的时候,肯定要给这种带参数的数据类型保留一席之地,否则后面的a作为参数就没有数据结构可供依附。只是这一层数据结构并不是显式的而已。
我们可以验证这点:
Prelude> j = Just 3 2
<interactive>:19:5: error:
• Couldn't match expected type ‘Integer -> t’
with actual type ‘Maybe Integer’
• The function ‘Just’ is applied to two arguments,
but its type ‘Integer -> Maybe Integer’ has only one
In the expression: Just 3 2
In an equation for ‘j’: j = Just 3 2
• Relevant bindings include j :: t (bound at <interactive>:19:1)
如上所示,我们给Just传两个数值作为参数,于是Haskell报错了:
• The function ‘Just’ is applied to two arguments,
but its type ‘Integer -> Maybe Integer’ has only one
说明Haskell可以判断出来Just只需要一个参数,我们却传了两个。因此,Maybe a后面这个a,实际上是一个参数,谁的参数?Maybe的参数,那Maybe叫什么?Maybe和Integer都算是data type吗?
其实在Haskell实现的时候,data type是两层实现的,从Maybe我们可以看出,如果不多出一层,就没法保存a这个参数。
这看不见的一层叫做kind。我们使用ghci的:k命令就可以查看数据类型的kind。比如Maybe:
Prelude> :k Maybe
Maybe :: * -> *
它的kind是* -> *。我们看看Integer的kind:
Prelude> :k Integer
Integer :: *
就是*。那么Maybe a的kind呢?
Prelude> type MaybeInteger = Maybe Integer
Prelude> :k MaybeInteger
MaybeInteger :: *
从上面我们可以看到:
- 不带参数,或者是已经把参数具体化的数据类型的kind是
* - 带参数的数据类型的kind是
* -> *
这样,我们就知道了Maybe是可以被引用到,并且它的kind是* -> *,因为它还有没具体化的参数。
注意这里面的*代表类型,而不代表正则表达式里面的通配符。
于是我们回过头看Functor的定义:
Prelude> :info Functor
class Functor (f :: * -> *) where
fmap :: (a -> b) -> f a -> f b
可以看到f指代kind为* -> *的数据类型,就是Maybe这样带参数的数据类型。
然后fmap里面的f a,指代被具体化类型的f,比如Maybe Integer,那么a就是Integer。
f b是一样的含义,比如Maybe Char,那么b就是Char。
因此fmap的定义就很清楚,对应到列表的map方法,就是:
Prelude> :t map
map :: (a -> b) -> [a] -> [b]
如上所示,[a]就是对应f a,因为[]就是带参数的类型:
Prelude> :k []
[] :: * -> *
大家把这篇文章的内容学好,可能有些抽象,但是下点功夫就可以掌握。
参考资料
- https://wiki.haskell.org/Kind
- http://stackoverflow.com/questions/27095011/what-exactly-is-the-kind-in-haskell
- https://www.haskell.org/onlinereport/decls.html#sect4.1.1
- https://www.haskell.org/tutorial/classes.html