模式匹配
代码语言:javascript复制Prelude> let { lucky' :: Integral a => a -> String; lucky' 7 = "seven"; lucky' x = "other" }
Prelude> lucky' 7
"seven"
Prelude> lucky' 10
"other"
Prelude> lucky' "a"
<interactive>:39:1: error:
• No instance for (Integral [Char]) arising from a use of ‘lucky'’
• In the expression: lucky' "a"
In an equation for ‘it’: it = lucky' "a"
tip: 在8.8.1版本ghci中如果按照书中的写法是会报没有匹配项的错误的,按照let { … } 写法则没有问题
在调用lucky时,模式会从上到下进行检查,一旦有匹配则对应的函数体便被应用。
如果我们制定的匹配模式不全时,传入一个没有被任何模式匹配到的参数时就会报错。
对Tuple同样可以使用模式匹配:
代码语言:javascript复制addVectors :: (Num a) => (a, a) -> (a, a) -> (a, a)
addVectors (x1, y1) (x2, y2) = (x1 x2, y1 y2)
first :: (a, b, c) -> a
first (x, _, _) = x
List Comprehension 也可以用模式匹配:
代码语言:javascript复制Prelude> let xs = [(1,3), (4,3), (2,4), (5,3), (5,6), (3,1)]
Prelude> [a b | (a,b) <- xs]
[4,7,6,8,11,4]
对List也可以用模式匹配:
代码语言:javascript复制Prelude> let {sumList :: Num a => [a] -> a; sumList [] = 0; sumList (x:xs) = x sumList xs }
Prelude> sumList [1,2,3]
6
Guards
代码语言:javascript复制bmiTell :: (RealFloat a) => a -> a -> String
bmiTell weight height
| weight / height ^ 2 <= 18.5 = "You're underweight, you emo, you!"
| weight / height ^ 2 <= 25.0 = "You're supposedly normal. Pffft, I bet you're ugly!"
| weight / height ^ 2 <= 30.0 = "You're fat! Lose some weight, fatty!"
| otherwise = "You're a whale, congratulations!"
guard由跟在函数名及参数后边的竖线标志,通常竖线都是靠右一个缩进排成一列。一个guard就是一个布尔表达式,如果是True,就使用对应的函数体。最后的一个guard往往是otherwise,它的定义就是简单一个otherwise = True。
通过guard实现自己的compare函数
代码语言:javascript复制-- myCompare.hs
myCompare :: Ord a => a -> a -> Ordering
a `myCompare` b
| a > b = GT
| a < b = LT
| otherwise = EQ
代码语言:javascript复制Prelude> :l myCompare.hs
[1 of 1] Compiling Main ( myCompare.hs, interpreted )
Ok, one module loaded.
*Main> 1 myCompare 2
<interactive>:2:1: error:
• Non type-variable argument
in the constraint: Num ((a -> a -> Ordering) -> t1 -> t2)
(Use FlexibleContexts to permit this)
• When checking the inferred type
it :: forall a t1 t2.
(Ord a, Num t1, Num ((a -> a -> Ordering) -> t1 -> t2)) =>
t2
*Main> 1 `myCompare` 2
LT
*Main> 2 `myCompare` 1
GT
*Main> 1 `myCompare` 1
EQ
where 关键字
上面例子中的bmiTell函数weight / height ^ 2重复计算了3次,可以利用where修改:
代码语言:javascript复制-- bmiTell.hs
bmiTell :: RealFloat a => a -> a -> String
bmiTell weight height
| bmi <= 18.5 = "case 1"
| bmi <= 25.0 = "case 2"
| bmi <= 30.0 = "case 3"
| otherwise = "otherwise"
where bmi = weight / height ^ 2
代码语言:javascript复制*Main> :l bmiTell.hs
[1 of 1] Compiling Main ( bmiTell.hs, interpreted )
Ok, one module loaded.
*Main> bmiTell 130 180
"case 1"
还可以继续修改:
代码语言:javascript复制bmiTell :: (RealFloat a) => a -> a -> String
bmiTell weight height
| bmi <= skinny = "You're underweight, you emo, you!"
| bmi <= normal = "You're supposedly normal. Pffft, I bet you're ugly!"
| bmi <= fat = "You're fat! Lose some weight, fatty!"
| otherwise = "You're a whale, congratulations!"
where bmi = weight / height ^ 2
skinny = 18.5
normal = 25.0
fat = 30.0
函数在where绑定中定义的名字只对当前函数可见。 where绑定也可以使用模式匹配
代码语言:javascript复制...
where bmi = weihgt / height ^ 2
(skinny, normal, fat) = (18.5, 25.0, 30.0)
where绑定可以定义名字也可以定义函数:
代码语言:javascript复制calcBmis :: (RealFloat a) => [(a, a)] -> [a]
calcBmis xs = [bmi w h | (w, h) <- xs]
where bmi weight height = weight / height ^ 2
let关键字
let绑定是个表达式,允许在任何地方定义局部变量,而对不同的guard不可见。let也可以使用模式匹配
代码语言:javascript复制cylinder :: (RealFloat a) => a -> a -> a
cylinder r h =
let sideArea = 2 * pi * r * h
topArea = pi * r ^2
in sideArea 2 * topArea
let的格式为let [binging] in [expression]
。let中绑定的名字仅在in中可见,let中的名字必须对齐在一列。
let是个表达式,而where是个语法结构。因为let是个表达式,所以let可以随处安放:
代码语言:javascript复制Prelude> [let square x = x * x in (square 5, square 3, square 2)]
[(25,9,4)]
上面的例子中let定义了一个函数。
若要在一行中绑定多个名字,可以用分号将他们分开:
代码语言:javascript复制Prelude> (let a = 100; b = 200; c = 300 in a*b*c, let foo = "Hey"; bar = "there" in foo bar)
(6000000,"Heythere")
tip: 最后那个绑定后面的分号不是必须的,可以加上可以去掉
可以用let改写上面的calcBmis函数:
代码语言:javascript复制calcBmis xs = [bmi w h | (w, h) <- xs, let bmi = w / h ^ 2]
List Comprehension 中 let 绑定的样子和限制条件差不多,只不过它做的不是过滤,而是绑定名字。let 中绑定的名字在输出函数及限制条件中都可见。这一来我们就可以让我们的函数只返回胖子的 bmi 值:
代码语言:javascript复制calcBmis xs = [bmi | (w, h) <- xs, let bmi = w / h ^ 2, bmi >= 25.0]
在 (w, h) <- xs 这里无法使用 bmi 这名字,因为它在 let 绑定的前面。
在 List Comprehension 中我们忽略了 let 绑定的 in 部分,因为名字的可见性已经预先定义好了。不过,把一个 let…in 放到限制条件中也是可以的,这样名字只对这个限制条件可见。在 ghci 中 in 部分也可以省略,名字的定义就在整个交互中可见。
代码语言:javascript复制Prelude> let a = 1
Prelude> a
1
Case 表达式
代码语言:javascript复制head' :: [a] -> a
head' xs = case xs of [] -> error "No head for empty lists!"
(x:_) -> x
case的语法:
代码语言:javascript复制case expression of pattern -> result
pattern -> result
pattern -> result
...
expression匹配符合的模式,如果符合则执行。实际上上面的模式匹配是case的语法糖而已。
函数参数的模式匹配只能用在定义函数时使用,而case可以用在任何地方:
代码语言:javascript复制describeList :: [a] -> String
describeList xs = "The list is " case xs of [] -> "empty."
[x] -> "a singleton list."
xs -> "a longer list."