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