「Haskellで書かれたおもしろいFizzBuzz」を再考する

よそ様の記事で「Haskellで書かれたおもしろいFizzBuzz」という記事がありますが、自分なりに説明し直してみます。

参考「[Haskellで書かれたおもしろいFizzBuzz ― Haskellで読めないコードに遭遇した時に解読する方法を徹底解説！ - プログラムモグモグ](https://itchyny.hatenablog.com/entry/2015/12/27/150000)」
参考「[I Did A Haskell: fizzbuzz : r/haskell](https://www.reddit.com/r/haskell/comments/2cum9p/i_did_a_haskell_fizzbuzz/)」

# 0. まとめ

元のコードは以下の通りです。

```haskell
let (m ~> str) x = str <$ guard (x `mod` m == 0)
in map (fromMaybe . show <*> 3 ~> "fizz" <> 5 ~> "buzz")
```

コードを読みやすくするために書き直すと以下のようになります。

```haskell
import Data.Foldable (for_)
import Data.Maybe (fromMaybe)

fizzBuzz :: Integer -> String
fizzBuzz x = fromMaybe (show x) fizzBuzzMaybe
  where
    fizzMaybe
        | x `mod` 3 == 0 = Just "Fizz"
        | otherwise      = Nothing
    buzzMaybe
        | x `mod` 5 == 0 = Just "Buzz"
        | otherwise      = Nothing
    fizzBuzzMaybe = fizzMaybe <> buzzMaybe

main :: IO ()
main = do
    for_ [1..20] $ \x -> do
        putStrLn $ fizzBuzz x
```

一時的に文字列 `"Fizz"` および `"Buzz"` を `Maybe` 型で扱い、`"Fizz"` でも `"Buzz"` でもなく `Nothing` の場合は `show x` を返します。

# 1. 準備

## 1.1. `let` 式を取り除く

Haskell のコーディングスタイルの 1 つとして、そもそも (`do` 記法等は例外として) `let` 式を用いない書き方があります。

`let` 式を用いないようにすると例えば以下のように書けます。

(`where` 句を使うこともできますが、ここでは単にコードを読みやすくするために演算子および関数を分けて定義します。)

```haskell
import Control.Monad (guard)
import Data.Foldable (for_)
import Data.Maybe (fromMaybe)

(~>) :: Integer -> String -> Integer -> Maybe String
(m ~> str) x = str <$ guard (x `mod` m == 0)

fizzBuzz :: [Integer] -> [String]
fizzBuzz = map (fromMaybe . show <*> 3 ~> "Fizz" <> 5 ~> "Buzz")

main :: IO ()
main = do

    let xs = fizzBuzz [1..20]

    for_ xs $ \x -> do
        putStrLn x
```

## 1.2. `map` 関数を取り除く

ここでは `for_` 関数でループして結果を表示するため、`map` 関数で一度結果をリストにする必要はありません。

`map` 関数を取り除くと以下のようになります。

```haskell
import Control.Monad (guard)
import Data.Foldable (for_)
import Data.Maybe (fromMaybe)

(~>) :: Integer -> String -> Integer -> Maybe String
(m ~> str) x = str <$ guard (x `mod` m == 0)

fizzBuzz :: Integer -> String
fizzBuzz = fromMaybe . show <*> 3 ~> "Fizz" <> 5 ~> "Buzz"

main :: IO ()
main = do
    for_ [1..20] $ \x -> do
        putStrLn $ fizzBuzz x
```

# 2. 演算子 `~>` の定義

演算子 `~>` は以下のように定義されています。

```haskell
(~>) :: Integer -> String -> Integer -> Maybe String
(m ~> str) x = str <$ guard (x `mod` m == 0)
```

意味的には以下と同じになります。

```haskell
(~>) :: Integer -> String -> Integer -> Maybe String
(m ~> str) x = do
    guard $ x `mod` m == 0
    pure str
```

```haskell
(~>) :: Integer -> String -> Integer -> Maybe String
(m ~> str) x =
    if x `mod` m == 0
    then Just str
    else Nothing
```

```haskell
(~>) :: Integer -> String -> Integer -> Maybe String
(m ~> str) x
    | x `mod` m == 0 = Just str
    | otherwise      = Nothing
```

参考「[guard - Control.Monad](https://hackage.haskell.org/package/base-4.19.0.0/docs/Control-Monad.html#v:guard)」
参考「[(<$) - Data.Functor](https://hackage.haskell.org/package/base-4.19.0.0/docs/Data-Functor.html#v:-60--36-)」

参考「[[Haskell] 条件分岐してモナドを返す - Qiita](https://qiita.com/kerupani129/items/607f438218b4a14c7e24)」
参考「[[Haskell] モナド 演算子 まとめ - Qiita](https://qiita.com/kerupani129/items/333155e5e2dee644d6dc)」

つまり、`m ~> str` は `x` が `m` の倍数のときに `Just str` を返す関数 ``(\x -> if x `mod` m == 0 then Just str else Nothing) :: Integer -> Maybe String`` を返します。

```haskell
fizzMaybe :: Integer -> Maybe String
fizzMaybe = 3 ~> "Fizz"

buzzMaybe :: Integer -> Maybe String
buzzMaybe = 5 ~> "Buzz"
```

意味的には以下と同じになります。

```haskell
fizzMaybe :: Integer -> Maybe String
fizzMaybe x
    | x `mod` 3 == 0 = Just "Fizz"
    | otherwise      = Nothing

buzzMaybe :: Integer -> Maybe String
buzzMaybe x
    | x `mod` 5 == 0 = Just "Buzz"
    | otherwise      = Nothing
```

# 3. 演算子 `<>` による関数同士の結合

演算子 `<>` は `Semigroup` 同士を結合する演算子です。

参考「[(<>) - Data.Semigroup](https://hackage.haskell.org/package/base-4.19.0.0/docs/Data-Semigroup.html#v:-60--62-)」

以下の理由により、`Integer -> Maybe String` 型の関数は `Semigroup` クラスのインスタンスです:

- `type String = [Char]`
    - `String` 型はリスト型
- `Semigroup [a]`
    - リスト型は `Semigroup`
- `Semigroup a => Semigroup (Maybe a)`
    - `Semigroup` を含む `Maybe` 型は `Semigroup`
- `Semigroup b => Semigroup (a -> b)`
    - `Semigroup` を返す関数は `Semigroup`

参考「[String - Data.String](https://hackage.haskell.org/package/base-4.19.0.0/docs/Data-String.html#t:String)」
参考「[Semigroup - Data.Semigroup](https://hackage.haskell.org/package/base-4.19.0.0/docs/Data-Semigroup.html#t:Semigroup)」

よって、以下のように `Integer -> Maybe String` 型の関数同士を演算子 `<>` で結合することができます。

```haskell
fizzBuzzMaybe :: Integer -> Maybe String
fizzBuzzMaybe = fizzMaybe <> buzzMaybe
```

意味的には以下と同じになります。

```haskell
fizzBuzzMaybe :: Integer -> Maybe String
fizzBuzzMaybe x = fizzMaybe x <> buzzMaybe x
```

# 4. 演算子 `.` による `a -> b` 型の関数と `b -> c -> d` 型の関数の合成

演算子 `.` は基本的には `(b -> c) -> (a -> b) -> a -> c` という関数の合成をしますが、`c` を `c -> d` に置き換えることで `(b -> c -> d) -> (a -> b) -> a -> c -> d` という関数の合成ができることが分かります。

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

ここでは `show :: Integer -> String` 関数と `fromMaybe :: String -> Maybe String -> String` 関数を合成して `Integer -> Maybe String -> String` 型の関数にします。

```haskell
f :: Integer -> Maybe String -> String
f = fromMaybe . show
```

意味的には以下と同じになります。

```haskell
f :: Integer -> Maybe String -> String
f x = fromMaybe $ show x
```

# 5. 演算子 `<*>` による `r -> a` 型の関数と `r -> a -> b` 型の関数の合成

演算子 `<*>` は基本的には `Applicative f => f (a -> b) -> f a -> f b` という計算をしますが、`f` を `(r ->)` に置き換えることで `(r -> a -> b) -> (r -> a) -> r -> b` という関数の合成ができることが分かります。

```haskell
(<*>) :: (r -> a -> b) -> (r -> a) -> r -> b
(<*>) f g x = f x $ g x
```

(コードが難解になるため、実用的には関数の合成に演算子 `<*>` を使用しない方が良いと思います。)

参考「[(<*>) - Control.Applicative](https://hackage.haskell.org/package/base-4.19.0.0/docs/Control-Applicative.html#v:-60--42--62-)」
参考「[Applicative - Control.Applicative](https://hackage.haskell.org/package/base-4.19.0.0/docs/Control-Applicative.html#t:Applicative)」

ここでは `Integer -> Maybe String -> String` 型の関数と `fizzBuzzMaybe :: Integer -> Maybe String` 関数を合成して `fizzBuzz :: Integer -> String` 関数にします。

```haskell
fizzBuzzMaybe :: Integer -> Maybe String
fizzBuzzMaybe x = fizzMaybe x <> buzzMaybe x

f :: Integer -> Maybe String -> String
f x = fromMaybe $ show x

fizzBuzz :: Integer -> String
fizzBuzz = f <*> fizzBuzzMaybe
```

意味的には以下と同じになります。

```haskell
fizzBuzzMaybe :: Integer -> Maybe String
fizzBuzzMaybe x = fizzMaybe x <> buzzMaybe x

f :: Integer -> Maybe String -> String
f x = fromMaybe $ show x

fizzBuzz :: Integer -> String
fizzBuzz x = f x $ fizzBuzzMaybe x
```

```haskell
fizzBuzzMaybe :: Integer -> Maybe String
fizzBuzzMaybe x = fizzMaybe x <> buzzMaybe x

fizzBuzz :: Integer -> String
fizzBuzz x = fromMaybe (show x) $ fizzBuzzMaybe x
```

# 6. 一部の関数を戻り値のみに置き換える

`fizzMaybe` 関数、`buzzMaybe` 関数および `fizzBuzzMaybe` 関数の引数は全て同じ値を渡します。

```haskell
fizzMaybe :: Integer -> Maybe String
fizzMaybe x
    | x `mod` 3 == 0 = Just "Fizz"
    | otherwise      = Nothing

buzzMaybe :: Integer -> Maybe String
buzzMaybe x
    | x `mod` 5 == 0 = Just "Buzz"
    | otherwise      = Nothing

fizzBuzzMaybe :: Integer -> Maybe String
fizzBuzzMaybe x = fizzMaybe x <> buzzMaybe x

fizzBuzz :: Integer -> String
fizzBuzz x = fromMaybe (show x) $ fizzBuzzMaybe x
```

よって、関数として扱わず戻り値のみを扱っても同様の計算ができます。

```haskell
fizzBuzz :: Integer -> String
fizzBuzz x = fromMaybe (show x) fizzBuzzMaybe
  where
    fizzMaybe
        | x `mod` 3 == 0 = Just "Fizz"
        | otherwise      = Nothing
    buzzMaybe
        | x `mod` 5 == 0 = Just "Buzz"
        | otherwise      = Nothing
    fizzBuzzMaybe = fizzMaybe <> buzzMaybe
```

`fizzMaybe <> buzzMaybe` の計算は以下のようになります。

```haskell
Nothing     <> Nothing     = Nothing
Just "Fizz" <> Nothing     = Just "Fizz"
Nothing     <> Just "Buzz" = Just "Buzz"
Just "Fizz" <> Just "Buzz" = Just "FizzBuzz"
```

`fromMaybe (show x) fizzBuzzMaybe` の計算は以下のようになります。

```haskell
fromMaybe (show x) Nothing           = show x
fromMaybe (show x) (Just "Fizz")     = "Fizz"
fromMaybe (show x) (Just "Buzz")     = "Buzz"
fromMaybe (show x) (Just "FizzBuzz") = "FizzBuzz"
```

# 7. 結果

コード全体は以下のようになります。

```haskell
import Data.Foldable (for_)
import Data.Maybe (fromMaybe)

fizzBuzz :: Integer -> String
fizzBuzz x = fromMaybe (show x) fizzBuzzMaybe
  where
    fizzMaybe
        | x `mod` 3 == 0 = Just "Fizz"
        | otherwise      = Nothing
    buzzMaybe
        | x `mod` 5 == 0 = Just "Buzz"
        | otherwise      = Nothing
    fizzBuzzMaybe = fizzMaybe <> buzzMaybe

main :: IO ()
main = do
    for_ [1..20] $ \x -> do
        putStrLn $ fizzBuzz x
```

```text:実行結果
1
2
Fizz
4
Buzz
Fizz
7
8
Fizz
Buzz
11
Fizz
13
14
FizzBuzz
16
17
Fizz
19
Buzz
```