Type

ghci中可以用:t检测表达式的类型

Prelude> :t "a"
"a" :: [Char]

函数也有类型,编写函数时给一个明确的类型声明是一个好习惯

removeNonUppercase :: [Char] -> [Char]
removeNonUppercase st = [c | c <- st, c `elem` ['A'..'Z']]

我们可以这样解读这个函数的类型:removeNonUppercase这个函数接收一个Char List类型的参数返回一个Char List类型的返回值

addThree :: Int -> Int -> Int -> Int
addThree x y z = x + y + z

参数之间由->分隔,这样解读这个函数的类型:addThree这个函数接收3个Int类型的参数返回一个Int类型的返回值。
> tip: 按照其他语言中的习惯,Int,Int,Int -> Int好像看起来更为恰当一些,但实际haskell中->只有一个作用:它标识一个函数接收一个参数并返回一个值,其中->符号左边是参数的类型,右边是返回值的类型。haskell中所有函数都是只接收一个参数的,所有函数都是currying的。

常见类型

  • Int 整数,与平台位数相关
  • Integer 无限大整数
  • Float 单精度浮点数
  • Double 双精度浮点数
  • Bool
  • Char

Tuple的类型取决于它的长度与其中项的类型,空Tuple也是一个类型,它只有一个值()

Type variables

以head函数为例

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

可以看到这里有个a,而a明显不是一个具体的类型,类型首字母必须是大写的,那它是什么呢,它实际上是一个类型变量,a可以是任意类型。 > tip: 与其他语言中的泛型generic很像

使用到类型变量的函数被称为“多态函数”。可以这样解读head函数的类型:head函数接收一个a类型的List参数(即任意类型的参数)返回一个a类型的返回值(参数与返回值的类型必须是一样的,都是a类型)
fst函数的类型:

Prelude> :t fst
fst :: (a, b) -> a

可以看到fst取一个包含两个型别的 Tuple 作参数,并以第一个项的型别作为回传值。这便是 fst 可以处理一个含有两种型别项的 pair 的原因。注意,a 和 b 是不同的型别变量,但它们不一定非得是不同的型别,它只是标明了首项的型别与回传值的型别相同。

Typeclass

如果一个类型属于某个typeclass,那它必定实现了Typeclass所描述的行为。

tip: 跟OOP中的接口很像

==函数的类型声明为例:

Prelude> :t (==)
(==) :: Eq a => a -> a -> Bool

这里的Eq就是typeclass, 这里意思是说a这个type必须是Eq的一个实现(相当于OOP中的a implement Eq)
=>符号左边的部分叫做类型约束

Eq这个Typeclass提供了判断相等性的接口,凡是可比较相等性的类型必定属于Eq class

elem函数的类型为:(Eq a)=>a->[a]->Bool这是因为elem函数在判断元素是否存在于list中时使用到了==的原因。

Show的成员为可用字符串表示的类型,操作Show Typeclass最常用的函数表示show。它可以取任一Show的成员类型并将其转为字符串

Prelude> show [1,2,3]
"[1,2,3]"
Prelude> show True
"True"

ReadShow相反,read函数可以将字符串转为Read的某成员类型

Prelude> read "5" - 2 
3
Prelude> read "True" || False
True

但是执行下面的代码,就会提示错误:

Prelude> read "5"
*** Exception: Prelude.read: no parse

这是因为haskell无法推导出我们想要的是一个什么类型的值,read函数的类型声明:

Prelude> :t read
read :: Read a => String -> a

它的回传值属于Read Typeclass,但是如果我们用不到这个值,它就无法推导出这个表达式的类型。所以我们需要在表达式后跟::的类型注释,以明确其类型:

Prelude> read "5" :: Int
5