《Haskell趣學指南》筆記之函式

方應杭在飢人谷發表於2019-05-04

系列文章


模式匹配

寫好模式,Haskell 會直接幫你匹配。如:

lucky :: Int -> String
lucky 7 = "7 is the lucky number!"
lucky x = "sorry, you are not lucky"
複製程式碼

載入之後執行

λ> lucky 2
"sorry, you are not lucky"
λ> lucky 3
"sorry, you are not lucky"
λ> lucky 7
"7 is the lucky number!"

複製程式碼

但是注意,如果把 lucky x 挪到 lucky 7 前面,就永遠匹配不到 lucky 7 了。

  • 用模式匹配實現階乘:
factorial :: Int -> Int 
factorial 0 = 1 
factorial n = n * factorial (n - 1)
複製程式碼
  • 定義模式的時候,一定在最後留一個萬能模式,否則可能會出錯
  • 元組的模式匹配
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)
複製程式碼
  • 模式中可以用 _ 來佔位,這個 _ 叫做泛變數
  • 可以用 x: _ 來匹配列表的第一個元素 x,但是要記得加圓括號,不然 Haskell 無法理解它
head' :: [a] -> a 
head' [] = error "Can' t call head on an empty list, dummy!" -- error 會中斷程式
head' (x:_) = x -- 這裡的圓括號不是 tuple 的標記
複製程式碼
  • 匹配只有一個元素的列表,可以用 [x],也可以用 (x:[])
  • 匹配只有兩個元素的列表,可以用 [x,y],也可以用 (x:y:[])
  • 匹配大於兩個元素的列表,用 (x:y:_),不能用 [] 的形式
  • as 模式:all@(x:items) 中的 all 表示整個 x:items 列表,方便後面引用

守衛/哨衛 guard

bmiTell :: Double -> String 
bmiTell weight height    
    | weight / height ^ 2 <= 18.5 = putStrLn "你體重不足,就像根竹竿!"
    | weight / height ^ 2 <= 25.0 = putStrLn "你體重正常,那你肯定是個醜逼!" &emsp; &emsp;
    | weight / height ^ 2 <= 30.0 = putStrLn "你體重超過,快減肥吧肥宅!" &emsp; &emsp;
    | otherwise &emsp;                      = putStrLn "你是豬!"
複製程式碼

其中的 bmi < 18.5 就是一個 guard。 每條 guard 語句至少縮排一個空格。 如果當前模式的守衛都沒有 True,而且也沒有寫 otherwise,就會進入下一個模式。

where

可以使用 where 來對計算結果進行快取,也可以定義其他的幫助函式:

bmiTell :: Double -> String 
bmiTell weight height &emsp; &emsp;
    | bmi <= 18.5 = putStrLn "你體重不足,就像根竹竿!"
    | bmi <= 25.0 = putStrLn "你體重正常,那你肯定是個醜逼!" &emsp; &emsp;
    | bmi <= 30.0 = putStrLn "你體重超過,快減肥吧肥宅!" &emsp; &emsp;
    | otherwise     = putStrLn "你是豬!"
    where bmi = weight / height ^ 2
             x = "whatever"
             getBmi weight height = weight / height ^ 2
複製程式碼

where 只在當前模式中有效。

就算沒有 guard,也可以在函式定義中使用 where:

calcBmis :: [(Double, Double)] -> [Double] 
calcBmis xs = [bmi w h | (w, h)< - xs] &emsp; &emsp; 
    where bmi weight height = weight / height ^ 2
複製程式碼

what 還能這樣用

describeList :: [a] -> String
describeList ls = "The list is " ++ what ls
    where what [] = "empty."
             what [x] = "a singleton list."
             what xs = "a longer list."
複製程式碼

其中 what ls 是以 ls 為引數呼叫 what 函式。

let

ghci> 4 * (let a = 9 in a + 1) + 2 
42
複製程式碼

let 和 where 相似,不同點在於 where 只允許我們在函式底部繫結變數,且對當前模式可見。而 let 可以出現在任何地方,且 let 裡面的變數只在 in 內有效。

另一個區別就是 let 是表示式(有值),而 where 不是。

case

case <exp> of pattern1 -> result1
                    pattern2 -> result2
                    pattern3 -> result3
複製程式碼

模式匹配不過是 case 的語法糖,而且模式匹配只能用在函式定義裡,而 case 可以用在任何地方。

相關文章