前面幾篇講了許多觀念,這篇比較瑣碎,主要是將一些函式中可用的語法一次講講,這幾個語法基本上是關於比對、流程語法、名稱等,彼此可以交互組合應用,某些程度上看這篇應該還輕鬆一些,多看幾個例子,就會知道怎麼應用了。
case .. of ..
在〈Haskell Tutorial(10)從 Tuple 初試模式比對〉談到了模式比對,在將值指定給變數的場合,都可以看到模式比對的應用,其中當然也包括了函式的參數,例如〈filter、map、fold 模式〉中的 map'
函式:
map' :: (Int -> Int) -> [Int] -> [Int]
map' mapper lt =
if null lt then lt
else (mapper $ head lt) : (map' mapper $ tail lt)
可以改寫為模式比對方式:
map' :: (Int -> Int) -> [Int] -> [Int]
map' _ [] = []
map' mapper (x:xs) = mapper x : map' mapper xs
實際上,前者不過是下面這個寫法的語法蜜糖:
map' :: (Int -> Int) -> [Int] -> [Int]
map' mapper lt =
case lt of [] -> []
x:xs -> mapper x : map' mapper xs
在函式參數上直接使用模式比對,是 case of
的一個特例,就 map'
函式這個例子來說,沒必要改用 case of
來自找麻煩,最主要的,case of
是個運算式,因此它可以用在任何地方:
descOddEven :: Int -> String
descOddEven n =
show n ++ " is " ++ case n `mod` 2 of 0 -> "Even"
1 -> "Odd"
where 與 let
上頭的 descOddEven
不是很好讀,下面這個或許會好一些:
descOddEven :: Int -> String
descOddEven n =
show n ++ " is " ++ oddOrEven n
where oddOrEven n = case n `mod` 2 of 0 -> "Even"
1 -> "Odd"
這個例子也可以改為 let in
:
descOddEven :: Int -> String
descOddEven n =
let oddOrEven n = case n `mod` 2 of 0 -> "Even"
1 -> "Odd"
in show n ++ " is " ++ oddOrEven n
差別在於,where
中定義的名稱,對整個函式中可見,而 let
中定義的名稱,只對 in
中的運算式可見,let in
也是個運算式,然而 where
只是語法,你不能在一個 =
之後接上 where
,或者在某個運算式之後接上 where
。
既然你知道,函式上參數的模式比對,不過是 case of
的一個特例而提供的語法蜜糖,那麼,在 where
中定義函式時,也可以使用模式比對就不足為奇了,下面這個函式可以求得費式數列:
fibonacciLt :: Int -> [Int]
fibonacciLt n =
[fibonacci x | x <- [0 .. n]]
where fibonacci 0 = 0
fibonacci 1 = 1
fibonacci n = fibonacci (n - 1) + fibonacci (n - 2)
使用 let
定義函式時,當然也可以用模式比對:
fibonacciLt :: Int -> [Int]
fibonacciLt n =
let fibonacci 0 = 0
fibonacci 1 = 1
fibonacci n = fibonacci (n - 1) + fibonacci (n - 2)
in [fibonacci x | x <- [0 .. n]]
guard
case of
或函式上參數的模式比對,實際上就是在比較值是否「符合」某個特定模式,那麼,對於大於、小於、大於等於、小於等於呢?使用 if else
當然是可以解決問題,不過,如果要比較的條件很多,就會形成一長串的 if else
:
grade :: Int -> String
grade score =
if score >= 90 then "A"
else if score >= 80 then "B"
else if score >= 70 then "C"
else if score >= 60 then "D"
else "E"
這種情況下,你可以使用 guard:
grade :: Int -> String
grade score
| score >= 90 = "A"
| score >= 80 = "B"
| score >= 70 = "C"
| score >= 60 = "D"
| otherwise = "E"
注意到,grade score
旁沒有接上 =
,=
是接在每個 guard 旁,對於 |
右邊條件式為 True
的情況下,會傳回 =
右邊的值。
當然,where
中也可以用 guard:
descGrade :: Int -> String
descGrade score =
"Your score is " ++ show score ++ " and is level " ++ grade score
where grade score
| score >= 90 = "A"
| score >= 80 = "B"
| score >= 70 = "C"
| score >= 60 = "D"
| otherwise = "E"
而 let
中也可以,只是不見得好讀罷了:
descGrade :: Int -> String
descGrade score =
let grade score
| score >= 90 = "A"
| score >= 80 = "B"
| score >= 70 = "C"
| score >= 60 = "D"
| otherwise = "E"
in "Your score is " ++ show score ++ " and is level " ++ grade score
怎麼使用這篇介紹到的語法,看範圍、看作用,也看可讀性,在這之間作個權衡!