阿男的小窝

View the Project on GitHub

Haskell的类型设计

Haskell是一个强类型的语言,所有元素皆有类型。以下是例子:

Prelude> :t 1
1 :: Num t => t
Prelude> :t 'a'
'a' :: Char
Prelude>
Prelude> :t "Hello"
"Hello" :: [Char]

如上代码所示,我们使用:t来显示元素的类型。比如数字1Num类型,aChar类型,Hello是Char的数组,显示为[Char]

Haskell是函数式语言,函数是它的核心,我们通过以下方式定义函数:

Prelude> add x = x + x

如上所示,我们定义了一个add函数,它接受一个参数x,它的功能是x + x,就是把输入翻倍。我们可以使用这个函数:

Prelude> add 2
4

如上所示,我们使用add函数,参数为2,结果输出为4,也就是2+2,和预期一致。

函数当然也有类型,我们同样可以使用:t来查看它的类型:

Prelude> :t add
add :: Num a => a -> a

如上所示,add函数的类型是a -> a,含义就是”输入为a,输出为a”,其中a是一种类型,它的类型是Num a

为什么我们的add定义是add x = x + x,而Haskell判断出add函数的输入输出类型是Num?因为使用了+加号,因为对参量做了相加,所以Haskell猜我们的参数是Num类型。

我们再写个函数:

Prelude> con x = x ++ x

我们定义了一个con函数,它接受一个x参量,做的运算是++,这个运算有很多类型的数据都支持,比如Haskell里的列表类型,字串(实际上Haskell没有String类型,字串上面看到了是Char数组)类型,都支持++,作用是把两个列表或字串合成一个。

因此,Haskell猜不了x的类型,那么这个con的类型定义会是怎样的?以下是答案:

Prelude> :t con
con :: [a] -> [a]

我们可以看到,Haskell并没有把类型”a”具体化,它只是抽象的(多态)。但它要求输入和输出都是一种类型(定义为”a”)。我们可以用用看自己定义的这个con函数:

Prelude> con "CAFE"
"CAFECAFE"
Prelude> con [1, 2, 3]
[1,2,3,1,2,3]

如上所示,con函数和我们分析的一样,既可以用在CAFE这种Char数组类型上,也可以用在列表类型上([1, 2, 3]是Haskell的列表类型,后续细讲),因为这两种类型都支持++操作符。

所以我们学到了一点:Haskell是强类型语言。

但是Haskell不强制我们定义类型,它会猜类型。但我们写函数的时候,可以明确类型:

Prelude> con :: [Char] -> [Char] ; con x = x ++ x
Prelude> :t con
con :: [Char] -> [Char]
Prelude> con "CAFE"
"CAFECAFE"

如上所示,我们明确定义了con的类型是[Char] -> [Char],即输入类型为Char数组,输出也一样是Char数组。然后我们使用:t验证了这点,最后我们使用了con函数。

注意:在上面的con函数定义中,类型和函数本身定义在了一行,用;分割:

con :: [Char] -> [Char] ; con x = x ++ x

这是因为Haskell的REPL,也就是GHCi,不支持多行定义。如果我们不写在一行,先写类型定义,会报错:

*Main> con :: [Char] -> [Char]

<interactive>:2:1: error:
	 No instance for (Show ([Char] -> [Char]))
		arising from a use of print
		(maybe you haven't applied a function to enough arguments?)
	 In a stmt of an interactive GHCi command: print it

但如果我们把代码保存在文件里面,命名为con.hs

$ cat con.hs
con :: [Char] -> [Char]
con x = x ++ x

然后在GHCi里面读入:

$ ghci
GHCi, version 8.2.2: http://www.haskell.org/ghc/  :? for help
Prelude> :l con
[1 of 1] Compiling Main             ( con.hs, interpreted )
Ok, one module loaded.

这样就是没有问题的。

最后我们试试来使用con函数应用在不是Char数组类型的数据上:

Prelude> con [1, 2, 3]

<interactive>:26:6: error:
	 No instance for (Num Char) arising from the literal 1
	 In the expression: 1
	  In the first argument of con, namely [1, 2, 3]
	  In the expression: con [1, 2, 3]

如上所示,我们看到,此时con不再接受这种类型,因为我们明确定义了con函数的类型:

*Main> :t con
con :: [Char] -> [Char]

Haskell的类型定义就讲到这里。