发布于 

语法

语法(模式匹配,哨卫)

|几天不写Blog了,先写个Haskell凑个数。估计再写两次就赶上现在学的进度了。

参考书籍: Learn you a haskell

模式匹配

定义

模式匹配(pattern matching)通过检查数据的特殊结构来检查是否匹配,并按模式从中解析出数据。

在Haskell中,函数经常要用到模式匹配,并且可以匹配一切数据类型。譬如,我们有一个检查传进的数是否是7的函数:

1
2
3
lucky :: Int -> String
lucky 7 = "LUCKY NUMBER SEVEN!"
lucky x = "Sorry, you're out of luck, pal!"
在调用lucky()时,函数会将传入的参数从上到下进行匹配,一旦有匹配,对应的函数体就会被调用。其功能其实类似于if-else语句。

如果我们在模式中给出一个小写字母,那这就是一个万能模式(catchall pattern)。他总能匹配输入的参数,并可以在后面调用。

这里还有一个斐波那契数列的例子:

1
2
3
factorial :: Int -> String
factorial 0 = 1
factorial n = n * factorial (n - 1)

这里,我们使用了递归来实现,并通过模式匹配来处理了边界值。后面我们会知道,这在Haskell中是十分常见的一种用法。

另外,模式匹配的最后一定要加入一个万能模式,即该函数必须能够处理所有情况,否则调用时用非常规值会发生错误。

元组的模式匹配

元组同样可以使用模式匹配。譬如下面的计算二维向量和的函数:

1
2
3
4
5
6
--不对元组内部进行模式匹配
addVectors :: (Double, Double) -> (Double, Double) -> (Double, Double)
addVectors a b = (fst a + fst b, snd a + snd b)
--对元组进行模式匹配
addVectors :: (Double, Double) -> (Double, Double) -> (Double, Double)
addVectors (x1, y1) (x2, y2) = (x1 + x2, y1 + y2)

占位符

在模式匹配时我们有可能只需要考虑其中的某些值,对于其他值不予考虑。这种情况一般发生在使用元组时。比如我们想要看元组的第一个数是否为7:

1
2
3
4
5
6
7
8
--我们固然可以使用万能匹配,如这样
lucky :: (Int, Int) -> String
lucky (7, y) = "Lucky for you!"
lcuky (x, y) = "You are not lucky!"
--但是我们也可以用占位符,像这样
lucky :: (Int, Int) -> String
lucky (7, _) = "Lucky for you!"
lucky (_, _) = "You are not lucky!"
使用占位符表示我们不关心它的内容,更常见的如取出序对的第一个数:
1
2
first :: (a,b,c) -> x
first (x, _, _) = x

列表和列表推导式的模式匹配

在列表推导式中也可以使用模式匹配:

1
2
3
ghci> let xs = [(1,3), (4,3), (2,4), (5,3), (5,6), (3,1)]
ghci> [a+b | (a,b) <- xs]
[4, 7, 6, 8, 11, 4]
我们可以使用[]来匹配空列表,也可以配合:来匹配非空列表(在Haskell中[1, 2, 3]就是1:2:3:[]的语法糖)。比如我们可以写一个head函数:
1
2
3
head :: [a] -> a
head [] = error "Can't call head on an empty list, dummy!"
head (x:_) = x
注意,当我们匹配列表时,对所有元素个数不同的列表都要进行匹配,譬如,我们若创建一个如下列表就会发生错误:
1
2
badAdd :: (Num a) => [a] -> a
badAdd (x:y:z:[]) = x + y + z

As模式

as模式(as-pattern)允许我们按模式把一个值分割成多个项,同时保留对整体的引用。要使用as模式,只要将一个名字和@置于普通模式的前面即可。

比如我们有一个取出第一个字符的函数定义如下:

1
2
3
firstletter :: String -> String
firstletter "" = ”Empty string, whoops!"
firstletter all@(x:xs) = "The first letter of " ++ all ++ "is " ++ [x]
使用时效果如下:
1
2
ghci> firstletter "Songer"
"The first letter of Songer is S"

哨卫

模式用来检查参数的结构是否匹配,哨卫(guard)则用来检查参数的性质是否为真。其亦与if语句相似,但是可读性更高。

我们这里先看一个用到哨卫的函数,根据你的分数按照A,B,C,D进行打分:

1
2
3
4
5
6
7
judge :: Int -> String
judge score
true| score<60 = "You got a D, fool!"
| score<75 = "You got a C!"
| score<85 = "You got a B,not bad!"
| score<=100 = "Congratulation, you got an A!"
| otherwise = "Error score!"

哨卫跟在竖线(|)的右边,一个哨卫就是一个布尔表达式,如果计算为True,则选择对应的函数体,否则计算下一个函数体。每条哨卫语句至少缩进一个空格。

与模式匹配一样,在最后一般要加上一个万能的情况以提升鲁棒性。


本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议,转载请注明出处。

本站由 [@Songer](https://blog.songer.xyz/) 创建,使用 Stellar 作为主题。