模式匹配

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同样可以使用模式匹配:

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 也可以用模式匹配:

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也可以用模式匹配:

Prelude> let {sumList :: Num a => [a] -> a; sumList [] = 0; sumList (x:xs) = x + sumList xs }
Prelude> sumList [1,2,3]
6

Guards

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函数

-- myCompare.hs
myCompare :: Ord a => a -> a -> Ordering
a `myCompare` b 
    | a > b = GT
    | a < b = LT
    | otherwise = EQ
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修改:

-- 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
*Main> :l bmiTell.hs
[1 of 1] Compiling Main             ( bmiTell.hs, interpreted )
Ok, one module loaded.
*Main> bmiTell 130 180
"case 1"

还可以继续修改:

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绑定也可以使用模式匹配

...
where bmi = weihgt / height ^ 2
    (skinny, normal, fat) = (18.5, 25.0, 30.0) 

where绑定可以定义名字也可以定义函数:

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也可以使用模式匹配

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可以随处安放:

Prelude> [let square x = x * x in (square 5, square 3, square 2)]
[(25,9,4)]

上面的例子中let定义了一个函数。

若要在一行中绑定多个名字,可以用分号将他们分开:

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函数:

calcBmis xs = [bmi w h | (w, h) <- xs, let bmi = w / h ^ 2]

List Comprehension 中 let 绑定的样子和限制条件差不多,只不过它做的不是过滤,而是绑定名字。let 中绑定的名字在输出函数及限制条件中都可见。这一来我们就可以让我们的函数只返回胖子的 bmi 值:

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 部分也可以省略,名字的定义就在整个交互中可见。

Prelude> let a = 1
Prelude> a
1

Case 表达式

head' :: [a] -> a  
head' xs = case xs of [] -> error "No head for empty lists!"  
                      (x:_) -> x  

case的语法:

case expression of pattern -> result  
                   pattern -> result  
                   pattern -> result  
                   ...  

expression匹配符合的模式,如果符合则执行。实际上上面的模式匹配是case的语法糖而已。

函数参数的模式匹配只能用在定义函数时使用,而case可以用在任何地方:

describeList :: [a] -> String  
describeList xs = "The list is " ++ case xs of [] -> "empty."  
                                               [x] -> "a singleton list."   
                                               xs -> "a longer list."