すごいHaskellたのしく学んでみる-その4
(週1で更新するのがお決まりパターンになってきました。)
今日のテーマは、、、
- 再帰
今日は一個のテーマで書いてみます。
Haskellでは繰り返し処理は再帰で書くようです。
再帰はたしかに読みやすいし、
コードが簡潔になるので、好きですが、
実行効率の面ではforやwhileの方が有利な為、
使い所に悩む時があります。
Haskellのコンパイラがどう最適化しているのかは興味がありますが、
とりあえず、やってみたいと思います。
例として、
「リストの中から条件に一致する要素を抽出する」
という処理を再帰で書いてみたいと思います。
まず基底部を考える
基底部とは、コレ以上分解できない所。
つまり解が明示的になる部分です。
まず、明確に答えが決まる場合として、
リストが空の場合、条件に一致しようがないので、
必ず空リストになります。
書いてみます。
find :: (Eq a) => (a -> Bool) -> [a] -> [a] find _ [] = []
リストの要素を何かしら比較したいので、Eqで型制約をつけ、
リストの要素を比較する関数とリストを使って、新しいリストを作成する、という定義にしました。
2行目は、どんな関数がこようと、リストが空だったら、結果は空リスト。
という意味になります。
問題を小さくして考える
次に、リストが空じゃなかった場合を考えます。
リストの第1要素から順番に比較して行くので、
一個目の要素を比較する事を考えます。
次に、残りの要素は、今やった事と同じ事を繰り返すようにします。
find :: (Eq a) => (a -> Bool) -> [a] -> [a] find _ [] = [] find f (x:xs) | f x = x : find f xs | otherwise = find f xs
ガードを使って書いてみました。
第1要素に関数fを適用し、結果がTrueであれば第1要素を採用し、
残りのリストをまたfindに渡します。
それ以外は、xは無視して、残りのリストをfindに渡します。
では、この関数を実行してみます。
*Main> find (\x -> x > 10 && x < 20) [1..200] [11,12,13,14,15,16,17,18,19]
うん、ちゃんと動きましたね。
ただ、このコードはDRYじゃないので、気持ち悪いです。
ちょっとだけリファクタリングしてみます。
find :: (Eq a) => (a -> Bool) -> [a] -> [a] find _ [] = [] find f (x:xs) | f x = x : next | otherwise = next where next = find f xs
whereを使って、同じ部分を関数にしてまとめました。
こんな書き方でも同じ事ができます。
find' :: (Eq a) => (a -> Bool) -> [a] -> [a] find' _ [] = [] find' f (x:xs) = let next = find' f xs in if f x then x : next else next
Haskell初心者の自分としては、
ガードを使った書き方の方が、なんとなく好き。
読みやすいし分かりやすい、ような気がします。
次回は、カリー化と高階関数について書いてみようかなと思います。