Haskell 中的类型
Haskell 中的类型与 Java,C 和 Python 等语言有共通之处,也有它独特的地方。
基础类型 Basic Types
Haskell 是一个静态类型的语言,使用类型推导来推导每个变量的类型。可以显式地通过 ::
来声明一个变量的类型,如
x :: Int
x = 2
注意当计算 x^2000
时,因为结果超出了 Int
的范围,Haskell 会返回 0
。这是因为 Int
是一个“有限”的类型,有最大和最小值(类似于 C, Java 中的 int
)。对应的 Integer
类型则是没有最大最小值的整数类型(类似于 Python 中的 int
)。
除了整数,Haskell 还支持一些常见的基本类型,如 Char
(字符),Double
(浮点),Bool
(布尔)。
复合类型 Compound Types
一个重要的数据类型是 List
。可以通过 [Int]
这样的方式来表示由 Int
组成的 List
。List
可以是任意长度,且所有元素的类型必须一致。一个特别的 List
是 [Char]
,它有一个别名 String
;String
和 [Char]
在 Haskell 中完全相同。
另一个重要的类型是 Tuple
,通过 (Int, String, Int)
这样的方式来表示。Tuple
的长度是固定的。
函数类型 Function Types
Haskell 的函数类型使用 ->
来分隔参数列表以及返回类型。比如
double :: Int -> Int
double n = n * 2
makeAddress :: Int -> String -> String -> (Int, String, String)
makeAddress number street town = (number,street,town)
由于函数是一等公民,所以也可以将函数作为另一个函数的参数类型。
ifEven :: (Int -> Int) -> Int -> Int
ifEven f n = if even n
then f n
else n
此外,Haskell 中也有用 lambda 来表示函数的语法,比如最简单的 identity 函数可以表示为 (\x -> x)
。这里使用反斜杠 \
听说是因为反斜杠长得很像 λ。
类型变量 Type Variables
有时我们希望一个函数能接受不同类型的参数,但是希望函数的返回值与参数类型相同(比较类似于 Java 中的泛型)。除了为所有类型都声明一个不同的函数之外,我们可以使用类型变量来表示我们对于类型的约束。比如以下 identity function 可以表示为:
simple :: a -> a
simple x = x
类型别名 Type Synonyms
就像 C 系语言有 typedef
一样,Haskell 也有类似的类型别名功能。我们使用 type
关键字来创建类型别名:
type FirstName = String
type LastName = String
type Age = Int
type Height = Int
type PatientName = (String,String)
代数数据类型 Algebraic Data Types
Haskell 中的代数数据类型类似于 Java 中的 enum
和 C 中的 struct
的结合体,使用 data
关键字来定义。比如
data Sex = Male | Female
data RhType = Pos | Neg
data ABOType = A | B | AB | O
等号左边的名称是类型,而等号右边使用 |
分隔开的是值构造器。值构造器可以接受参数,且值构造器的名字可以和类型的名字相同,比如
data BloodType = BloodType ABOType RhType
定义函数时,我们可以根据值构造器来做模式匹配,比如
showName :: Name -> String
showName (Name f l) = f ++ " " ++ l
showName (NameWithMiddle f m l) = f ++ " " ++ m ++ " " ++ l
记录语法 Record Syntax
当一个值构造器的参数过多时,我们很难记住使用它们的顺序,此时我们可以使用记录语法来给这些参数一个名字。这个语法有些类似于 Python 中的 namedtuple
比如
data Patient = Patient { name :: Name
, sex :: Sex
, age :: Int
, height :: Int
, weight :: Int
, bloodType :: BloodType }
当我们构造一个 Patient
时,就可以根据参数的名字给每个参数指定值。
jackieSmith :: Patient
jackieSmith = Patient {name = Name "Jackie" "Smith"
, age = 43
, sex = Female
, height = 62
, weight = 115
, bloodType = BloodType O Neg }
此时我们我们可以使用参数的名字来获取一个 Patient
的参数。
GHCi> height jackieSmith
62
GHCi> showBloodType (bloodType jackieSmith)
"O-"
我们也可以复制一个 Patient
,只需要将需要更改的参数放在花括号内就可以。
jackieSmithUpdated = jackieSmith { age = 44 }