[Haskell] IO () 型の for ループ

Haskell は関数型プログラミング言語のため、他のプログラミング言語における「`for` ループ」そのものはありません。

ただし、モナドを返す場合はそれに似た書き方が可能です。

# 1. 再帰

Haskell ではモナドかどうかにかかわらず、再帰でループすることができます。

本記事では深く触れませんが、`fix` 関数を用いると無名関数で再帰することができます。

```haskell
import Control.Monad (when)
import Data.Function (fix)

f :: Int -> IO ()
f x = do
    when (x > 1) $ do
        f $ x - 1
    putStrLn $ "f: " ++ show x

main :: IO ()
main = do

    f 3

    fix
        (\ (f' :: Int -> IO ()) (x :: Int) -> do
            when (x > 1) $ do
                f' $ x - 1
            putStrLn $ "fix: " ++ show x
        )
        3

    flip fix 3 $ \ (f' :: Int -> IO ()) (x :: Int) -> do
        when (x > 1) $ do
            f' $ x - 1
        putStrLn $ "flip fix: " ++ show x
```

```text:実行結果
f: 1
f: 2
f: 3
fix: 1
fix: 2
fix: 3
flip fix: 1
flip fix: 2
flip fix: 3
```

参考「[fix - Data.Function](https://hackage.haskell.org/package/base-4.19.0.0/docs/Data-Function.html#v:fix)」

# 2. モナドをループさせる関数

モナドをループさせる関数として以下のようなものがあります:

- `traverse_ f xs`, `for_ xs f`
    - `xs` の各要素 `x` に関数 `f` を適用する
- `replicateM_ n x`
    - `x` を `n` 回繰り返す
- `foldM_ f a xs`
    - `xs` の各要素 `x` に関数 `f` を適用する
    - 「関数 `f` の戻り値 `pure a'`」の `a'` を次のループ時の「関数 `f` の引数 `a`」とする
- `zipWithM_ f xs ys`
    - `xs` の各要素 `x` と `ys` の各要素 `y` を対にして関数 `f` を適用する
- `sequenceA_ xs`
    - `xs` の各要素 `x` を実行する

※ `for_ = flip traverse_` です。
※他に歴史的な理由により残されている関数がありますが、`mapM_ = traverse_`, `forM_ = for_` および `sequence_ = sequenceA_` です。

※各関数の型の説明は後述。

参考「[traverse_ - Data.Foldable](https://hackage.haskell.org/package/base-4.19.0.0/docs/Data-Foldable.html#v:traverse_)」
参考「[for_ - Data.Foldable](https://hackage.haskell.org/package/base-4.19.0.0/docs/Data-Foldable.html#v:for_)」
参考「[replicateM_ - Control.Monad](https://hackage.haskell.org/package/base-4.19.0.0/docs/Control-Monad.html#v:replicateM_)」
参考「[foldM_ - Control.Monad](https://hackage.haskell.org/package/base-4.19.0.0/docs/Control-Monad.html#v:foldM_)」
参考「[zipWithM_ - Control.Monad](https://hackage.haskell.org/package/base-4.19.0.0/docs/Control-Monad.html#v:zipWithM_)」
参考「[sequenceA_ - Data.Foldable](https://hackage.haskell.org/package/base-4.19.0.0/docs/Data-Foldable.html#v:sequenceA_)」

## 2.1. 使い方

`IO ()` 型に関して各関数は以下のように使用します。

```haskell
import Control.Monad (foldM_, replicateM_, zipWithM_)
import Data.Foldable (for_, sequenceA_, traverse_)

main :: IO ()
main = do

    traverse_
        (\ (x :: Int) -> do
            putStrLn $ "traverse_: " ++ show x
        )
        [1..3]

    for_ [1..3] $ \ (x :: Int) -> do
        putStrLn $ "for_: " ++ show x

    replicateM_ 3 $ do
        putStrLn "replicateM_"

    foldM_
        (\ (a :: Int) (x :: Int) -> do
            putStrLn $ "foldM_: " ++ show x ++ ", " ++ show a
            pure $ a + 1
        )
        4
        [1..3]

    zipWithM_
        (\ (x :: Int) (y :: Int) -> do
            putStrLn $ "zipWithM_: " ++ show x ++ ", " ++ show y
        )
        [1..3]
        [4..6]

    sequenceA_
        [ putStrLn "sequenceA_: 1"
        , putStrLn "sequenceA_: 2"
        , putStrLn "sequenceA_: 3"
        ]
```

```text:実行結果
traverse_: 1
traverse_: 2
traverse_: 3
for_: 1
for_: 2
for_: 3
replicateM_
replicateM_
replicateM_
foldM_: 1, 4
foldM_: 2, 5
foldM_: 3, 6
zipWithM_: 1, 4
zipWithM_: 2, 5
zipWithM_: 3, 6
sequenceA_: 1
sequenceA_: 2
sequenceA_: 3
```

## 2.2. 型

各関数の型は以下のようになっています。

```haskell
traverse_   :: (Foldable t, Applicative f) => (a -> f b) -> t a -> f ()
for_        :: (Foldable t, Applicative f) => t a -> (a -> f b) -> f ()

replicateM_ :: Applicative m => Int -> m a -> m ()

foldM_      :: (Foldable t, Monad m) => (b -> a -> m b) -> b   -> t a -> m ()
zipWithM_   :: Applicative m         => (a -> b -> m c) -> [a] -> [b] -> m ()

sequenceA_  :: (Foldable t, Applicative f) => t (f a) -> f ()
```

ここで `t = []`, `f = IO` および `m = IO` とすると、以下のようになります。

```haskell
traverse_   :: (a -> IO b) -> [a] -> IO ()
for_        :: [a] -> (a -> IO b) -> IO ()

replicateM_ :: Int -> IO a -> IO ()

foldM_      :: (b -> a -> IO b) -> b   -> [a] -> IO ()
zipWithM_   :: (a -> b -> IO c) -> [a] -> [b] -> IO ()

sequenceA_  :: [IO a] -> IO ()
```

戻り値が不要なところを `IO ()` 型とすると、以下のようになります。

```haskell
traverse_   :: (a -> IO ()) -> [a] -> IO ()
for_        :: [a] -> (a -> IO ()) -> IO ()

replicateM_ :: Int -> IO () -> IO ()

foldM_      :: (b -> a -> IO b ) -> b   -> [a] -> IO ()
zipWithM_   :: (a -> b -> IO ()) -> [a] -> [b] -> IO ()

sequenceA_  :: [IO ()] -> IO ()
```