越而胜己 / Beyond awesome

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 组成的 ListList 可以是任意长度,且所有元素的类型必须一致。一个特别的 List[Char],它有一个别名 StringString[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 }