Skip to content

枚举类型

haskell
data Fruit = Apple | Banana | Orange
 deriving Show

以上使用data声明了一个枚举类型, 有AppleBananaOrange三个值。

deriving Show 表示自动为类型生成 show的 type class实例, 以便编译器知道如何打印它

haskell
ghci> Orange
Orange
ghci> fruit1 :: Fruit = Apple
ghci> fruit1
Apple
ghci> fruits :: [Fruit] = [Apple, Apple, Orange]
ghci> fruits

以上我们可以看到枚举的使用方式。

枚举可以进行模式匹配:

haskell
ghci> :{
ghci| isMyLove :: Fruit -> Bool
ghci| isMyLove Banana = True
ghci| isMyLove _ = False
ghci| :}
ghci> isMyLove Apple
False
ghci> isMyLove Banana
True

以上定义了一个将Fruit枚举转换为bool值的函数, 当传入Banana时返回true, 否则返回false

代数数据类型

事实上, 枚举只是haskell中代数数据类型的一种使用方式。

代数数据类型 (Algebraic data type, ADT) 指由其他类型的数据组合成的类型。

积类型: 积类型的值通常由多个值组成, 称为字段。 就如haskell中的元组、c语言中的结构体, 积类型的所有实例都有相同的类型组合, 也就是说都拥有相同数量、类型的字段。

和类型/联合: 和类型的值会被分为多种类别,称为变体。 每种变体允许有不同的类型定义, 通常由构造函数来实现, 构造函数定义了参数数量、类型。枚举类型是和类型的一个特例,即所有构造函数都没有参数。

超越枚举--haskell中的自定义类型

使用data可以定义和类型, 语法格式如:

haskell
data T = C1 arg11Type
    | C2 arg21Type arg22Type
    | C3

这表示类型T有三种构造函数C1, C2, C3, 构造函数名后紧跟参数的定义。

以下是一个例子, Person类型有两个构造函数P1P2, 其中 P1有nameage两个参数, 而P2只有name参数, P3没有任何参数

haskell
-- person's name, age
data Person = P1 String Int
      | P2 String
      | P3
 deriving Show

注意, 构造函数名称与它的类型名称是允许相同的, 在不同的场景, haskell会将它解释为不同的函数(构造函数 / 类型)

haskell
data Person = Person String Int Int

使用这些构造函数时, 就像调用一个正常函数一样。

haskell
ghci> person1 = P1 "张三" 30
ghci> -- 构造函数必须传入每一个参数
ghci> person2 = P1 "李四"
ghci> person2

<interactive>:61:1: error:
 No instance for (Show (Int -> Person))
        arising from a use ofprint
        (maybe you haven't applied a function to enough arguments?)
 In a stmt of an interactive GHCi command: print it
ghci> person3 = P2 "李四"
ghci> person3
P2 "\26446\22235"

自定义类型同样可以使用模式匹配, 它将构造函数的参数值从实例中解构出来

haskell
getName :: Person -> String
getName (P1 name _)  = name
getName (P2 name) = name
getName P3 = "unkown"

更高层次上理解模式匹配

在之前的学习中, 我们已经使用了很多数据类型的模式匹配。

haskell
-- Int
func 123 = true
-- String
func "abc" = true
-- list
func x : xs = x

但从根本上来说, 模式匹配只干一件事: 找到传入的参数值是由哪个构造函数构造的, 然后从中解构出值。

模式匹配的一些规则:

  1. 下划线可以作为通配符, 能匹配任何内容
  2. 使用@你可以在解构的同时, 仍然可以引用整个值
haskell
showName :: Person -> String
showName p@(P1 name _) = "The name field of (" ++ show p ++ ") is " ++ name
  1. 模式匹配可以嵌套, 在解构出来的参数基础上可以继续对它进行解构

模式匹配式的类型定义为

haskell
pat ::= _
     |  var
     |  var @ ( pat )
     |  ( Constructor pat1 pat2 ... patn )

为了理解func 123 = true这样的值匹配, 你可以把Int类型的定义想象成是这样的一个枚举

haskell
data Int  = 0 | 1 | -1 | 2 | -2 | ...

递归数据类型

haskell中定义递归数据类型与其他语言类似: 将该类型某个成员的类型定义为该类型即可。

但递归最重要的就是要有终止条件, 否则将无限递归没有意义。

对于和类型来说, 只要有一种变体不是递归的, 那就是有终止条件啦!

下面我们来定义一个链表:

haskell
data LinkList = Empty | Node Int LinkList

嗯...不错不错, 我们定义了一个LinkList联合类型, 它有两种变体(构造函数), 其中Empty表示空结点, 而Node表示正常结点, 存储这一个整数, 以及一条链表。

事实上, haskell内置list的实现与此非常相似, 不过它有专属的语法糖[], :

case表达式

haskell中模式匹配的基本结构是case表达式。

haskell
case exp of
  pat1 -> exp1
  pat2 -> exp2
  ...

事实上, haskell定义函数的语法正是case表达式的语法糖。

haskell
-- 函数语法
getName :: Person -> String
getName (P1 name _)  = name
getName (P2 name) = name
getName P3 = "unkown"
-- 使用case表达式
getName :: Person -> String
getName p = case p of
 (P1 name _) -> name
 (P2 name) -> name
 P3 -> "unkown"

BST

好了, 经过上面的学习, 我们学习到了如何自定义类型, 如何在函数中使用自定义类型, 如何定义递归类型, 现在来实现一个简单的搜索树:

haskell
newtype NodeData = NodeData Int
  deriving (Show, Eq)

data BST = Leaf | Node BST NodeData BST
  deriving (Show, Eq)

data CompareResult = LESS | EQUAL | GREATER
  deriving (Show, Eq)

compareNodeData :: NodeData -> NodeData -> CompareResult
compareNodeData (NodeData x) (NodeData y)
  | x == y = EQUAL
  | x < y = LESS
  | x > y = GREATER

-- 在二叉搜索树中插入一个元素
insert :: BST -> NodeData -> BST
insert Leaf nodeData = Node Leaf nodeData Leaf
insert (Node left nodeDataRoot right) nodeData = case compareNodeData nodeData nodeDataRoot of
  EQUAL -> Node left nodeData right
  LESS -> Node (insert left nodeData) nodeDataRoot right
  GREATER -> Node left nodeDataRoot (insert right nodeData)

-- 中序遍历
inOrder :: BST -> [NodeData]
inOrder Leaf = []
inOrder (Node left nodeData right) = inOrder left ++ [nodeData] ++ inOrder right

-- 从列表构建二叉搜索树
build :: [NodeData] -> BST
build = foldl insert Leaf

参考

代数数据类型 - 维基百科,自由的百科全书 (wikipedia.org)

Product type - Wikipedia