Haskell:Kind

在之前的文章里,我们学习了Functor class,并且知道了Functor里面定义了fmap

Functor classfmap的定义是这样的:

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 aa保持这种类型参数的状态。

但是不管指不指定具体类型,我们的数据的类型不会是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叫什么?MaybeInteger都算是data type吗?

其实在Haskell实现的时候,data type是两层实现的,从Maybe我们可以看出,如果不多出一层,就没法保存a这个参数。

这看不见的一层叫做kind。我们使用ghci的:k命令就可以查看数据类型的kind。比如Maybe

Prelude> :k Maybe
Maybe :: * -> *

它的kind是* -> *。我们看看Integer的kind:

Prelude> :k Integer
Integer :: *

就是*。那么Maybe akind呢?

Prelude> type MaybeInteger = Maybe Integer
Prelude> :k MaybeInteger
MaybeInteger :: *

从上面我们可以看到:

这样,我们就知道了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 []
[] :: * -> *

大家把这篇文章的内容学好,可能有些抽象,但是下点功夫就可以掌握。

参考资料