Haskell 的类型系统健壮且强大。类型是可读性、安全性、可维护性的重中之重。类型(types),亦称数据类型(datatypes)提供了维护大型系统的必备机制,并允许我们用更少的代码编写更多的功能。
先前已经介绍了数字、字符、字符串类型。它们是标准库中的标准数据类型(standard datatypes)。
何谓类型?
表达式在被求值时,会归约为值。每个值都有类型。类型将一系列值聚合在一起,并提取共通点。这种共通点可能是抽象的;也可能是特定概念或领域的模型。Haskell 的类型离不开集合,将类型视作集合,可以帮助理解 Haskell 语言。
集合论是类型论的基石。Haskell 具有析取(disjunction, or)、合取(conjunction, and)的等价物。
类型声明
从最简单的类型声明(data declarations)开始:
data Bool = False | True
-- [1] [2] [3] [4]- 这是数据类型
Bool的构造器。类型的名字会出现在类型签名(type signature)中 False的构造器- 管道
|表示集合类型(sum type),亦即逻辑析取(logical disjunction),或(or)。所以,Bool的值为True或False True的构造器
并不是所有类型声明都如此。有的使用逻辑合取(logical
conjunction),和(and);有的构造器需要实参。共通的是,它们都以
data 关键字开头,后跟名称,并用等号表示定义。
可以通过 :info <Type> 查找某个类型的声明:
ghci> :info Bool
type Bool :: *当然也可以查找函数的:
ghci> :i not
not :: Bool -> Bool -- Defined in ‘GHC.Classes’这时,我们使用的是 Bool 的类型签名。
还可以将其用作值:
ghci> not True
False数字类型
- 整数,所有的整数,不分正负。
Int:此类型为定点数(fixed-precision),也就是说它有范围,具有最大值和最小值限制。值得一提的是,该值根据实现有所不同,通常为 32 位或 64 位,且至少应有 30 位。Integer:也是整数。但支持任意大或任意小的整数。
- 小数,包含以下类型
Float:单浮点数。Double:双浮点数。Rational:分数。Rational是任意精确的,但不如Scientific有效率。Scientific:节省空间且几乎任意精度的科学数字类型。它将系数存为 Integer,指数存为 Int。由于 Int 不是任意大的,因此精度并不是无限的,但很难到达。Scientific可在library2(Hackage)中使用,并且可以使用cabal install或stack install进行安装。
这些数字类型都有类型 Num 的实例。
Num
是一个类型类(typeclass),大多数数字类型都具有其实例,因为其具有一系列标准操作。比如
(+) (-) (*) 操作符。
Int
Int 类型是追求更高性能时的产物。大多数程序应该使用
Integer,除非理解类型的限制,并理解性能影响。
Int 及其相关类型 Int8 Int16
Int64 等具有危险性,因为它们不是任意大小的。
ghci> import GHC.Int
ghci> (127 + 1) :: Int8
-128
ghci> 128 :: Int8
<interactive>:25:1: warning: [-Woverflowed-literals]
Literal 128 is out of the Int8 range -128..127
If you are trying to write a large negative literal, use NegativeLiterals
-128Int 及其衍生类型都是 Bounded
的。可以这样查看最大值和最小值:
ghci> maxBound :: Int8
127
ghci> minBound :: Int8
-128小数
一般而言,最常用的是 Double。Float
用于图形学,例如和 OpenGL 交互。
有些计算是小数的,而非整数的,比如 (/):
ghci> :t (/)
(/) :: Fractional a => a -> a -> a注解 Fractional a => 表示一个类型类限制(typeclass
constraint),这里的 \(a\) 必须实现
Fractional 类型类。
Fractional 类型类需要实例类也有 Num
类型类的实例。可以说,Num 是 Fractional
的超集。
比较
ghci> x = 5
ghci> x == 5
True
ghci> x == 6
False
ghci> x < 7
True
ghci> x > 3
True
ghci> x /= 5
False因为等号 = 已经表示了赋值,所以使用 ==
表示判断等于。另外 /= 表示不等于。
可以查看它们的类型:
ghci> :t (==)
(==) :: Eq a => a -> a -> Bool
ghci> :t (<)
(<) :: Ord a => a -> a -> Bool请注意,这里也有类型类限制。Eq
表示可以判断相等;Ord 表示可以比较。
不仅限于数字,也可以对字符或字符串排序:
ghci> 'a' > 'b'
False
ghci> 'b' > 'a'
True
ghci> 'b' == 'c'
False
ghci> 'b' /= 'c'
True
ghci> "Julie" > "Chris"
True
Prelude> ['a', 'b'] > ['b', 'a']
False
Prelude> 1 > 2
False
Prelude> [1, 2] > [2, 1]
False若一个数据类型没有 Ord 实例,那么这些函数不起作用。
ghci> data Mood = G | B deriving Show
ghci> [G, B] > [B, G]
<interactive>:59:8: error:
• No instance for (Ord Mood) arising from a use of ‘>’
• In the expression: [G, B] > [B, G]
In an equation for ‘it’: it = [G, B] > [B, G]布尔
先前已经知道 Bool 的类型声明:
data Bool = False | True这里介绍一些函数:
- 非
Prelude> let x = 5
Prelude> not (x == 5)
False
Prelude> not (x > 7)
True- 与
Prelude> True && True
True
Prelude> (8 > 4) && (4 > 5)
False
Prelude> not (True && True)
False- 或
Prelude> False || True
True
Prelude> (8 > 4) || (4 > 5)
True
Prelude> not ((8 > 4) || (4 > 5))
Falseif-then-else
Haskell 没有 if 语句,但有 if 表达式。
ghci> let t = "Truthin'"
ghci> let f = "Falsin'"
ghci> if True then t else f
"Truthin'"因为条件为 True,所以返回 t。
结构如下:
if CONDITION
then EXPRESSION_A
else EXPRESSION_BCONDITION 必须归约为 Bool,若值为
True 则返回 EXPRESSION_A,若为
False 则返回
EXPRESSION_B。EXPRESSION_A 和
EXPRESSION_B 的值类型必须一致。
来看一个例子:
-- greetIfCool.hs
module GreetIfCool where
greetIfCool1 :: String -> IO ()
greetIfCool1 coolness =
if cool
then putStrLn "So cool"
else putStrLn "pshhhhhh"
where
cool = coolness == "I'm cool"
greetIfCool2 :: String -> IO ()
greetIfCool2 coolness =
if cool coolness
then putStrLn "So cool"
else putStrLn "pshhhhh"
where
cool a = a == "I'm cool"元组
元组(tuple)是一类允许你在单个值中储存多个值的类型。元组具有特殊的语法,二元组(two-tuple,
pair)这样写,(x, y);三元组(three-tuple,
triple)这样写,(x, y, z),等等。元组所含元素的数量亦称元数(arity)。元组中的几个元素,类型不必是相同的。
可以查看 (,) 的定义:
ghci> :info (,)
data (,) a b = (,) a b不同于 Bool,元组有显著不同。其一,它有两个形参,由
\(a\) 和 \(b\)
表示。它们都需被应用于实际类型。其二,它为积类型(product
type),而非和类型。积类型表示逻辑合取:你必须同时提供两个实参以构建值。
请注意,两个类型参数是不同的,所以这允许元组有两个不同类型的值。当然,元组类型不需要不同。
ghci> (,) 8 10
(8,10)
ghci> (,) 8 "aaa"
(8,"aaa")
ghci> (,) False 'a'
(False,'a')在 Haskell 中,双元组有一些便携函数:
ghci> let myTup = (1 :: Integer, "blah")
ghci> myTup = (1 :: Integer, "blah")
ghci> :t myTup
myTup :: (Integer, String)
ghci> fst myTup
1
ghci> snd myTup
"blah"
ghci> import Data.Tuple
ghci> swap myTup
("blah",1)双元组的 (x, y)
语法是特殊的。类型和构造器在语法层面上长得一样,但实质上它们不同。
当编写函数时,也可以用该语法来模式匹配(pattern match)。例如:
fst' :: (a, b) -> a
fst' (a, b) = a
snd' :: (a, b) -> b
snd' (a, b) = b
tupFunc :: (Int, [a])
-> (Int, [a])
-> (Int, [a])
tupFunc (a, b) (c, d) =
((a + c), (b ++ d))通常而言,使用太大的元组是不明智的。建议至多使用元数为 5 的元组。
列表
类似于元组,列表(list)也用于将多个值包含到单个值中。无论如何,列表有显著的不同:
- 所有元素的类型必须一致
- 列表具有特殊语法
[]。也用于同时表达类型和值。 - Haskell 的列表是不定长的。在类型上不能确定元素有多少个。
这里有一个示例:
ghci> p = "Papuchon"
ghci> awesome = [p, "curry", ":)"]
ghci> awesome
["Papuchon","curry",":)"]
ghci> :t awesome
awesome :: [[Char]]这里的 awesome 是 Char 列表的列表。因为
String 是 [Char] 的类型别名。并且
String
被包含在一个列表中。因此,任何值都可以被存在列表中。
我们继续:
ghci> s = "The Simons"
ghci> also = ["Quake", s]
ghci> awesome ++ also
["Papuchon","curry",":)","Quake","The Simons"]
ghci> allAwesome = [awesome, also]
ghci> allAwesome
[["Papuchon","curry",":)"],["Quake","The Simons"]]
ghci> :t allAwesome
allAwesome :: [[String]]
ghci> :t concat
concat :: Foldable t => t [a] -> [a]
ghci> concat allAwesome
["Papuchon","curry",":)","Quake","The Simons"]
ghci> concat $ concat allAwesome
"Papuchoncurry:)QuakeThe Simons"更详细的介绍将在以后进行。
以上,就是数据类型基础的内容。
