Haskell - 模式匹配

Haskell - 模式匹配

模式匹配

引子:假设我们要一个函数,这个函数可以处理一个数是不是7。按照之前的思路,我们可以这样写:

1
2
3
isSevenV1 x = if x == 7 then "Is seven" else "Is not seven"
main = do
print $ isSevenV1

那么如果是判断一个数的是0-10中的哪一个,是否这样写会稍微麻烦一点,不过可能想到使用列表[(1,"one"),(2,"two")]这样来处理。

那么或许有一个不错的方法——模式匹配

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
isSevenV1 x = if x == 7 then "Is seven" else "Is not seven"



isSevenV2 :: Int -> String
isSevenV2 7 = "Is seven"
isSevenV2 x = "Is not seven"


sayNumber ::Int -> String
sayNumber 1 = "One"
sayNumber 2 = "Two"
sayNumber 3 = "Three"
sayNumber 4 = "Four"
sayNumber 5 = "Five"
sayNumber x = "Not between 1 and 5"



main = do

print $ isSevenV1 7
print $ isSevenV2 8
print $ isSevenV2 7


print $ sayNumber 3
print $ sayNumber 10

在调用函数时,会将传入的参数从上往下顺序检查,一旦有匹配,对应的函数就会被调用。 如果给出一个x,y,z这样的参数,那么他就成为了一个万能模式,可以匹配到所有的参数, 一个好处就是可以处理最后情况之外的部分。
当然如果将万能模式丢到了函数最上面,那么很抱歉,万能模式函数之后的函数就永远都不会匹配到了。

使用模式匹配完成一个求阶乘

1
2
3
4
5
6
factorial :: Int -> Int
factorial 0 = 1
factorial n = n * factorial (n - 1)

main = do
print $ factorial 5

不过有些函数看起来可以正常运行,但是实际上却会出发runtime error。

1
2
3
4
charName :: Char -> Int
charName 'a' = 1
charName 'b' = 2
charName 'c' = 3

如果我们在调用函数的时候,使用charName 'h'。 可以得到Non-exhaustive因为这套模板没有不够全面,没有考虑到’a’ ‘b’ ‘c’三者之外的情况。那么程序也就会发生不可预料的问题,这里其实也有一定的防御式编程思想。

元组的模式匹配

1
2
3
4
5
6
7
8
9
10
11
addVectorsV1 :: (Double, Double) -> (Double, Double) -> (Double, Double)
addVectorsV1 a b = (fst a + fst b, snd a + snd b)

addVectorsV2 :: (Double, Double) -> (Double, Double) -> (Double, Double)
addVectorsV2 (x1, y1) (x2, y2) = (x1 + x2, y1 + y2)



main = do
print $ addVectorsV1 (1, 2) (3, 4)
print $ addVectorsV2 (1, 2) (3, 4)

虽然我们可以使用addVectorV1的方式去处理两个或者多个元组的之间的操作。针对于每一个元组用fst和snd取双元组的两个元素。可以运行,没有问题。但是有更加漂亮的做法addVectorV2
参数直接上两个元组,还有了名字,用两个字来说:优雅。

实现一个三元组

1
2
3
4
5
6
7
8
tupleFirst :: (a,b,c) -> a
tupleFirst (x,_,_) = x

tupleSecond :: (a,b,c) -> b
tupleSecond (_,y,_) = y

tuledThird :: (a,b,c) -> c
tuledThird (_,_,z) = z

不用关心具体是什么类型,也不需要管具体的内容,给上一个泛变量就ok了。

列表与列表推导式匹配

1
2
xs = [(1,3), (4,3), (2,4), (5,3), (5,6)]
[a + b | (a,b) <- xs]

可以得到如下结果

1
[4,7,6,8,11] 

做一个简单的head函数
([1,2,3] 只是一个 1:2:3:[]的语法糖)

1
2
3
head` :: [a] -> a
head` [] = error 'Can not call head on a empty list'
head` (x:_) = x

As模式

允许按照模式将一个值分割成多个项,同时保留对其整体的引用

1
2
3
4
5
6
7
firstLetter :: String -> String
firstLetter "" = "Empty string"
firstLetter all@(x:xs) = "The first letter of " ++ all ++ " is " ++ [x]

main = do
print $ firstLetter ""
print $ firstLetter "Hello"

这个函数接受一个字符串参数,并返回一个字符串。
firstLetter :: String -> String

函数的实现分为两种情况:

  1. 如果输入的字符串为空字符串(””),则返回字符串 “Empty string”。
  2. 如果输入的字符串非空,则返回字符串 “The first letter of [all] is [first letter]”。其中 [all][first letter] 分别表示输入的字符串 all 和第一个字母 first letter

函数的实现使用了一个As模式all@(x:xs),它表示一个字符串 all,其中 x 是字符串 xs 的第一个字符。这里或许会因为变量名字觉得理解,xs是否是x+s这种情况,实际上也可以命名为(firstLetter : exceptFirstLetter)也是一样的。这也是上面说的列表的语法糖。

1
all@(x:xs) = "The first letter of " ++ all ++ " is " ++ [x]

这个匿名函数的目的是将字符串 all 的第一个字母替换为输入字符串 all 的第一个字母,并将结果与字符串 [x] 连接起来。


Haskell - 模式匹配
http://cvrain.cloudvl.cn/2023/11/16/Haskell/haskell-pattern-matching/
作者
ClaudeRainer
发布于
2023年11月16日
许可协议