Type
ghci中可以用:t
检测表达式的类型
Prelude> :t "a"
"a" :: [Char]
函数也有类型,编写函数时给一个明确的类型声明是一个好习惯
代码语言:javascript复制removeNonUppercase :: [Char] -> [Char]
removeNonUppercase st = [c | c <- st, c `elem` ['A'..'Z']]
我们可以这样解读这个函数的类型:removeNonUppercase这个函数接收一个Char List类型的参数返回一个Char List类型的返回值
代码语言:javascript复制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函数为例
代码语言:javascript复制Prelude> :t head
head :: [a] -> a
可以看到这里有个a,而a明显不是一个具体的类型,类型首字母必须是大写的,那它是什么呢,它实际上是一个类型变量,a可以是任意类型。 > tip: 与其他语言中的泛型generic很像
使用到类型变量的函数被称为“多态函数”。可以这样解读head函数的类型:head函数接收一个a类型的List参数(即任意类型的参数)返回一个a类型的返回值(参数与返回值的类型必须是一样的,都是a类型) fst函数的类型:
代码语言:javascript复制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"
Read
与Show
相反,read函数可以将字符串转为Read的某成员类型
Prelude> read "5" - 2
3
Prelude> read "True" || False
True
但是执行下面的代码,就会提示错误:
代码语言:javascript复制Prelude> read "5"
*** Exception: Prelude.read: no parse
这是因为haskell无法推导出我们想要的是一个什么类型的值,read函数的类型声明:
代码语言:javascript复制Prelude> :t read
read :: Read a => String -> a
它的回传值属于Read Typeclass,但是如果我们用不到这个值,它就无法推导出这个表达式的类型。所以我们需要在表达式后跟::
的类型注释,以明确其类型:
Prelude> read "5" :: Int
5