枚举类型
data Fruit = Apple | Banana | Orange
deriving Show
以上使用data
声明了一个枚举类型, 有Apple
、Banana
、Orange
三个值。
deriving Show 表示自动为类型生成 show的 type class实例, 以便编译器知道如何打印它
ghci> Orange
Orange
ghci> fruit1 :: Fruit = Apple
ghci> fruit1
Apple
ghci> fruits :: [Fruit] = [Apple, Apple, Orange]
ghci> fruits
以上我们可以看到枚举的使用方式。
枚举可以进行模式匹配:
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
可以定义和类型, 语法格式如:
data T = C1 arg11Type
| C2 arg21Type arg22Type
| C3
这表示类型T有三种构造函数C1, C2, C3
, 构造函数名后紧跟参数的定义。
以下是一个例子, Person类型有两个构造函数P1
、P2
, 其中 P1有name
和age
两个参数, 而P2
只有name
参数, P3
没有任何参数
-- person's name, age
data Person = P1 String Int
| P2 String
| P3
deriving Show
注意, 构造函数名称与它的类型名称是允许相同的, 在不同的场景, haskell会将它解释为不同的函数(构造函数 / 类型)
haskelldata Person = Person String Int Int
使用这些构造函数时, 就像调用一个正常函数一样。
ghci> person1 = P1 "张三" 30
ghci> -- 构造函数必须传入每一个参数
ghci> person2 = P1 "李四"
ghci> person2
<interactive>:61:1: error:
• No instance for (Show (Int -> Person))
arising from a use of ‘print’
(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"
自定义类型同样可以使用模式匹配, 它将构造函数的参数值从实例中解构出来
getName :: Person -> String
getName (P1 name _) = name
getName (P2 name) = name
getName P3 = "unkown"
更高层次上理解模式匹配
在之前的学习中, 我们已经使用了很多数据类型的模式匹配。
-- Int
func 123 = true
-- String
func "abc" = true
-- list
func x : xs = x
但从根本上来说, 模式匹配只干一件事: 找到传入的参数值是由哪个构造函数构造的, 然后从中解构出值。
模式匹配的一些规则:
- 下划线可以作为通配符, 能匹配任何内容
- 使用
@
你可以在解构的同时, 仍然可以引用整个值
showName :: Person -> String
showName p@(P1 name _) = "The name field of (" ++ show p ++ ") is " ++ name
- 模式匹配可以嵌套, 在解构出来的参数基础上可以继续对它进行解构
模式匹配式的类型定义为
pat ::= _
| var
| var @ ( pat )
| ( Constructor pat1 pat2 ... patn )
为了理解
func 123 = true
这样的值匹配, 你可以把Int类型的定义想象成是这样的一个枚举haskelldata Int = 0 | 1 | -1 | 2 | -2 | ...
递归数据类型
haskell中定义递归数据类型与其他语言类似: 将该类型某个成员的类型定义为该类型即可。
但递归最重要的就是要有终止条件, 否则将无限递归没有意义。
对于和类型来说, 只要有一种变体不是递归的, 那就是有终止条件啦!
下面我们来定义一个链表:
data LinkList = Empty | Node Int LinkList
嗯...不错不错, 我们定义了一个LinkList联合类型, 它有两种变体(构造函数), 其中Empty
表示空结点, 而Node
表示正常结点, 存储这一个整数, 以及一条链表。
事实上, haskell内置list的实现与此非常相似, 不过它有专属的语法糖[], :
case表达式
haskell中模式匹配的基本结构是case表达式。
case exp of
pat1 -> exp1
pat2 -> exp2
...
事实上, haskell定义函数的语法正是case表达式的语法糖。
-- 函数语法
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
好了, 经过上面的学习, 我们学习到了如何自定义类型, 如何在函数中使用自定义类型, 如何定义递归类型, 现在来实现一个简单的搜索树:
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