『すごいHaskellたのしく学ぼう!』を読み進めていたら、再帰関数のところでいきなり躓きました。

最初に出てきたのがこのコードです。

replicate' :: Int -> a -> [a]
replicate' n x
    | n <= 0    = []
    | otherwise = x : replicate' (n-1) x

まず x : の書き方がよく分かりませんでした。ChatGPTに聞いたら、リストの先頭に要素を一個くっつける演算子とのこと。うわー、そうだった。なるほど、こういうときに使うのか、という感じです。大事なポイントとして「左側は要素、右側はリストじゃないといけません」と教わりました。

分かったつもりで本を進めようとしたら、その次の一文でまた止まります。「負のケースに対応するために、ここではパターンではなくガードを使っています。」……どういうこと?

負の数を考えなければ、こう書けるみたいです。

replicate' :: Int -> a -> [a]
replicate' 0 x = []
replicate' n x = x : replicate' (n-1) x

なるほど、パターンマッチでも書ける。けど、条件(<=みたいなの)が付けられないと。
でもどっちみちガードの方が見やすいな、と思いました。

再帰はどこで止まるのか

ここで一番こんがらがったのが、「この再帰、どこで止まるんだ?」というところでした。

あ、そっか。n <= 0 があるから、再帰的に呼び出していっても最後に0になったときに止まるのか……。いや、ん?そもそも再帰が止まる条件って何だっけ?

しばらく考えて、ようやく腑に落ちました。再帰は何かのスイッチで止まるというより、自分の中で自分を呼び出さずに [] を返すから止まるんですね。

そう気づいてから、このコードがすごく分かりやすく見えてきました。

-- 1. 止まる条件
| n <= 0 = []

-- 2. 止まる条件に近づく呼び出し
| otherwise = x : replicate' (n-1) x

「止まる条件」と「そこに近づく変化」のセットで考えると、再帰の怖さが少し減りました。

ガードとパターンマッチが一緒に出てきた

次に出てきたのがこの関数です。

take' :: Int -> [a] -> [a]
take' n _
  | n <= 0 = []
take' _ [] = []
take' n (x:xs) = x : take' (n-1) xs

突然ガードとパターンマッチが一緒に出てきて、ビックリしました。こんな書き方もありなんだ。

でも、さっきの「止まる条件」と「変化」のセットで分けて考えたら、なんとか分かった気がします。

take' n _
  | n <= 0 = []
take' _ [] = []

これが止まる条件(2つ)で、

take' n (x:xs) = x : take' (n-1) xs

これが変化、ということなんだと思います。とはいえ、こんなの自分で考えつける気がしないんですが、慣れてくるもんなんでしょうか。

ちなみに、この「止まる条件」のことを「基底部」と呼ぶらしいです。ちょっと前のページに書いてあったみたいですが、そのときは頭がついていってませんでした。

そして結局、再帰関数は頭がこんがらがって面倒くさくなってしまったので、最後のクイックソートのところだけ写経して終わりました。また気が向いたら読み返そうと思います。