03 类型和类型类

2016-09-10  本文已影响231人  勤劳的悄悄

类型

Haskell 是强类型和静态类型的,所有的数据都有明确的类型

静态类型的好处

表达式的类型

ghci> :t 'a'
'a' :: Char  

ghci> :t True  
True :: Bool  

ghci> :t "HELLO!"  
"HELLO!" :: [Char]  

ghci> :t (True, 'a')  
(True, 'a') :: (Bool, Char) 

ghci> :t 4 == 5  
4 == 5 :: Bool

函数的类型

函数也有明确的类型,包括参数的类型和返回值的类型。

比如前面那个过滤大写字母的函数,输入一个字符串,输出一个字符串,类型如下

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

注意: [Char]String 是等价的

多个参数的函数,类型之间用 -> 分隔,最后一项是输出类型

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

几种常见的基本类型

类型 说明
Int 32 位整数,有边界
Integer 大整数,没有边界
Float 单精度浮点数,精度稍差
Double 双精度浮点数,精度较高
Bool 布尔值
Char 字符类型

类型变量

有的时候,函数在定义的时候参数的类型不能确定,等到使用时参数传入后才可以确定类型,这时可以用一个变量代替参数类型。

比如处理列表的 head 函数。列表可以是任意类型的列表,返回值只要和列表中元素的类型一致即可。

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

类型变量都使用单个小写字母

类型类入门

TypeClasses 叫类型类,他是对类型约束和规范,他要求类型必须满足某些规格,有点类似接口。

类型被某种 TypeClasses 约束后,和这种 TypeClasses 相关的函数就可以处理被其约束的类型,

下面是 == 函数的类型声明,该函数的参数被 Eq 约束,说明这个参数可以被 Eq 相关的函数所处理

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

读作: 相等函数取两个相同类型的值作为参数,传回一个布尔值。两个参数的类型必须符合 Eq 类型类的约束

Eq 规定了判断相等性的规范,凡是能够比较相等性的类型都必须被 Eq 类约束,比如数字、字符、字符串等等

ghci> 5 == 5
True

ghci> 5 /= 5
False

ghci> 'a' == 'a'
True

ghci> "Ho Ho" == "Ho Ho"
True

ghci> 3.432 == 3.432
True

elem 函数中,因为其参数要使用 == ,所以也被 Eq 约束

ghci> :t elem
(Eq a)=>a->[a]->Bool

基本的类型类

Eq

任何需要使用相等性判断的类型,即希望该类型能够被 ==/= 等函数处理,都要受 Eq 约束

Ord

需要比较大小,能够被 <, >, <=, >=, compare 等相关函数处理的类型,都受它约束

注意:要成为 Ord 的成员,首先需要加入 Eq

ghci> "Abrakadabra" < "Zebra"  
True  

ghci> "Abrakadabra" `compare` "Zebra"  
LT  

ghci> 5 >= 2  
True  

ghci> 5 `compare` 3  
GT
Show

show 函数可以将一个数据对象转换成字符串

任何需要能转换成字符串,并能够被 show 函数处理的的类型,都受它约束

ghci> show 3  
"3"  

ghci> show 5.334  
"5.334"  

ghci> show True  
"True"
Read

read 函数可以将字符串转换成一个数据对象,他是 show 函数的反函数

ReadShow 正好相反,任何类型想要获取从字符串转换成对象的能力,即能够被 read 函数处理,都受它约束

ghci> read "True" || False  
True  

ghci> read "8.2" + 3.8  
12.0 

ghci> read "5" - 2  
3  

ghci> read "[1,2,3,4]" ++ [3]  
[1,2,3,4,3]

转换时须明确类型

需要注意的是,如果字符串转换的类型不能推断,则需要明确指定,否则会报错

下面代码中,4 既可以转换成 Int 类型,也可以转换成 Integer Float 类型,因为没有明确指定,编译器会报错

ghci> read "4"  
< interactive >:1:0:  
    Ambiguous type variable `a' in the constraint:  
      `Read a' arising from a use of `read' at <interactive>:1:0-7  
    Probable fix: add a type signature that fixes these type variable(s)

下面明确指定类型

ghci> read "5" :: Int  
5  

ghci> read "5" :: Float  
5.0  

ghci> (read "5" :: Float) * 4  
20.0  

ghci> read "[1,2,3,4]" :: [Int]  
[1,2,3,4]  

ghci> read "(3, 'a')" :: (Int, Char)  
(3, 'a')

Enum

连续类型,有前置和后继,能够被 succpred 等函数处理。

相关类型有 (), Bool, Char, Ordering, Int, Integer, Float 和 Double

ghci> ['a'..'e']  
"abcde"  

ghci> [LT .. GT]  
[LT,EQ,GT]  

ghci> [3 .. 5]  
[3,4,5]  

ghci> succ 'B'  
'C'
Bounded

有上下边界的类型,能够被 minBoundmaxBound 等函数处理

ghci> minBound :: Int  
-2147483648  

ghci> maxBound :: Char  
'\1114111' 

ghci> maxBound :: Bool  
True  

ghci> minBound :: Bool  
False

注意,如果元组中的分量都是有边界的,那么元组也是有边界的

ghci> maxBound :: (Bool, Int, Char)  
(True,2147483647,'\1114111')

Num

如果你的类型需要进行四则运算,即能够使用 + - * / 等相关函数,则应该被 Num 约束

下面的代码说明, * 要求的参数类型虽然还不确定,但是只要符合 Num 规范就可以,因此传给他 Int Float 都可以

ghci> :t (*)  
(*) :: (Num a) => a -> a -> a

注意,乘号两边的数字类型必须一致,否则会报错,比如

(5 :: Int) * (6 :: Integer)
Integral

对所有整数的规范

Floating

对所有浮点数的规范

重要函数 fromIntegral

这个函数会将一个对象的约束条件从 Integral 转换成 Num,受 Num 约束的数是更通用的数,可以使用更多的函数

fromIntegral :: (Num b, Integral a) => a -> b

历史问题

由于历史原因,某些函数会返回整数类型,而不是通用数字类型。这时候如果用返回值和其他数字类型运算会出错。

举例来说,length 函数的型别声明为:

length :: [a] -> Int

如果用 length 的返回值,给它加一个浮点数 3.2 就会报错,因为这是将浮点数和整数相加。

这时候就需要

fromIntegral (length [1,2,3,4]) + 3.2

</br>

上一篇 下一篇

猜你喜欢

热点阅读