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]
|
可以得到如下结果
做一个简单的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
函数的实现分为两种情况:
- 如果输入的字符串为空字符串(””),则返回字符串 “Empty string”。
- 如果输入的字符串非空,则返回字符串 “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]
连接起来。