いろいろなモナド at Nakameguro.hs #2

# 発表者紹介

- 亀岡 亮太
- [株式会社HERP](https://herp.co.jp/) リードエンジニア
  - Haskell とか TypeScript とか
  - [We're hiring!](https://herp.co.jp/careers/)
- GitHub [@ryota-ka](https://github.com/ryota-ka)
- Twitter [@ryotakameoka](https://twitter.com/ryotakameoka)

---

# はじめに

---

# はじめに

前半戦 👉 [モナドに至る道 at Nakameguro.hs #2](https://qiita.com/lotz/items/8e66442a329183d5a3ce)

> モナドは型クラスのひとつです。型クラスは、異なるデータ型の間に見られる共通の性質を抽象化するためのものでした。モナド自体の性質を睨み続けるのではなく、`Monad` 型クラスのインスタンスになっている `Maybe` `Either e` `[]` `Reader r` などの具体的なデータ型を紹介し、モナドに対する気持ちを醸成することを目指します。

- `Monad` は型クラスのひとつだった
- 型クラスは，**異なる型に共通する性質を抽象化するもの**だった
  - `Show`: 文字列にできる
  - `Ord`: 大小関係の比較ができる
  - `Functor`: 関数を**文脈**の中に持っていける
  - `Monad`: **文脈**を持った計算を**繋げて書ける**
- `Monad` のインスタンスになる「異なる型」を色々見て回って**気持ち**を醸成する
  - **繋げて書ける**感を味わってもらう (`>>=` `>=>`)
  - **潰せる**感を味わってもらう (`join`)

---

# アジェンダ (話すこと)

- `Functor`-`Applicative`-`Monad` recap
- `Maybe` 
  - `a -> Maybe b` の繋げ方
  - `Maybe (Maybe a)` の潰し方
- `Either e`
  - `a -> Either e b` の繋げ方
  - `Either e (Either e a)` の潰し方
- `[]`
  - `a -> [b]` の繋げ方
  - `[[a]]` の潰し方
- `Reader r`
  - `a -> Reader r b` の繋げ方
  - `Reader r (Reader r a)` の潰し方
- `Identity a`
  - `a -> Identity b` の繋げ方
  - `Identity (Identity a)` の潰し方

---

# 話さないこと

- モナドの定義・モナドとはズバリ何であるか
  - モナドは単なる自己関手の圏におけるモノイド対象だよ．なにか問題でも？
- モナド則
- その他発展的なトピック
  - → 懇親会で🍻

---

# この資料内のルール

- `($)` は極力使いません
  - `(` `)` の方が脳内でパーズしやすいので
- kind をできるだけ明示します
  - `-XKindSignatures`
  - 最初は脳内での推論が難しいので
- `*` の代わりに `Type` と書きます
  - `*` を `Type` と読むのは難しいので
  - `-XNoStarIsType`
  - `import GHC.Types (Type)`
- 型クラスのメソッドの実装に際して型を併記します
  - `-XInstanceSigs`
- `newtype` のコンストラクタの prefix に `Mk` を付けます
  - 型の名前とコンストラクタの名前が一緒だと紛らわしいので
- 冗長な `(` `)` を入れています
  - 脳内でのパーズがしやすいように

---

# `Functor`-`Applicative`-`Monad` recap

---

# `Functor`-`Applicative`-`Monad` recap

前半のおさらいなのでサクッと

`m` の kind が `Type` -> `Type` である必要がある
(型引数を1つ取る)

```haskell
class Functor (m :: Type -> Type) where
    fmap :: (a -> b) -> (m a -> m b)
```

```haskell
class Functor m => Applicative (m :: Type -> Type) where
    pure  :: a -> m a
    (<*>) :: m (a -> b) -> m a -> m b
```

```haskell
class Applicative m => Monad (m :: Type -> Type) where
    (>>=) :: m a -> (a -> m b) -> m b

join  :: Monad m => m (m a) -> m a
(>=>) :: Monad m => (a -> m b) -> (b -> m c) -> (a -> m c)
```

---

# `Functor`-`Applicative`-`Monad` recap

```haskell
(<$>) ::   (a ->   b) -> (m a -> m b) -- Functor
(<*>) :: m (a ->   b) -> (m a -> m b) -- Applicative
(=<<) ::   (a -> m b) -> (m a -> m b) -- Monad
```

- `(=<<)` の `a -> m b` に注目
  - **計算**をする度に `m` に包まれて返ってくる
    - 二重・三重・……と増えるとしんどい
    - 高々一重で抑えたい
      - `join :: Monad m => m (m a) -> m a`
        - 二重になっている `m` を一重に**潰せる**

---

# `Maybe`

---

# `Maybe`

```haskell
data Maybe a
    = Nothing -- 値がない (ちょうど0個の値がある)
    | Just a  -- 値がある (ちょうど1個の値がある)
```

```haskell
> :kind Maybe -- 👇ヨシ！
Maybe :: Type -> Type
```

---

# `a -> Maybe b` の繋げ方

`a -> Maybe b` なる計算は，**失敗するかもしれない**計算だと思うことができる

- 計算が成功したとき : `Just` に包んで値を返し，値が**ある**ことを表明する
- 計算が失敗したとき : `Nothing` を返し，値が**ない**ことを表明する


```haskell
-- n が偶数ならば，n を 2 で割った値を Just に包んで返す．
-- そうでなければ Nothing を返す
div2 :: Int -> Maybe Int
div2 n | even n    = Just (div n 2) -- 偶数なら成功
       | otherwise = Nothing        -- 奇数なら失敗
```

そのような計算を繋げて書きたい！💡

```haskell
-- どうやって"繋げる"？
f :: a -> Maybe b
g :: b -> Maybe c
```

```haskell
-- どうやって"潰す"？
join :: Maybe (Maybe a) -> Maybe a
```

---

# `a -> Maybe b` の繋げ方

(わざとらしいが) 実用的な例

- 環境変数 `PORT` を探す
    - 見つからなかった
        - `Nothing` を返す
    - 見つかった
        - 整数としてパーズする
            - 失敗した
                - `Nothing` を返す
            - 成功して `port :: Int` が得られた
                - `0` 以上かつ `65535` 以下か？
                    - はい
                        - `Just port` を返す
                    - いいえ
                        - `Nothing` を返す

---

# `a -> Maybe b` の繋げ方

愚直に `case ~ of ...` で書いた例

```haskell
inRange :: Int -> Bool
inRange n = 0 <= n && n <= 65535
```

```haskell
lookup :: Eq a => a -> [(a, b)] -> Maybe b
lookup = _
```

```haskell
lookupPortFromEnv :: [(String, String)] -> Maybe Int
lookupPortFromEnv env =
    let mportStr = lookup "PORT" env          -- mportStr :: Maybe String
    in  case mportStr of                      -- PORT環境変数あった？
            Nothing      -> Nothing           --   → なかった
            Just portStr ->                   --   → あった
                let mport = readMaybe portStr -- mport :: Maybe Int
                in  case mport of             -- 整数としてパーズできた？
                        Nothing   -> Nothing  --   → できなかった
                        Just port ->          --   → できた
                            if inRange port   -- port :: Int
                                then Just port
                                else Nothing
```

---

# `a -> Maybe b` の繋げ方

`>>=` を使って書き直すと

```haskell
lookupPortFromEnv' :: [(String, String)] -> Maybe Int
lookupPortFromEnv' env =
    lookup "PORT" env >>= (\portStr ->  -- portStr :: String が既に取れている体で計算を続けられる
        readMaybe portStr >>= (\port -> -- port :: Int が既に取れている体で計算を続けられる
            if inRange port
                then Just port
                else Nothing
        )
    )
```

`case ~ of ...` が消えた！

---

# `a -> Maybe b` の繋げ方

- `case ~ of ...` が消えた！
  - → なんで！？

`Maybe` に対する `Monad` のインスタンス宣言の中で，既に場合分けが書かれている

```haskell
instance Monad Maybe where
    (>>=) :: Maybe a -> (a -> Maybe b) -> Maybe b
    Nothing  >>= _ = Nothing -- Nothing の場合には，「続きの計算」を捨てて打ち止めにする
    (Just x) >>= k = k x     -- Just x の場合には，中の値 x を「続きの計算」に渡す
```

---

# `a -> Maybe b` の繋げ方

`do` 構文を用いて書き直すと

```haskell
lookupPortFromEnv'' :: [(String, String)] -> Maybe Int
lookupPortFromEnv'' env = do
    portStr <- lookup "PORT" env
    port    <- readMaybe portStr
    if 0 <= port && port <= 65535
        then Just port
        else Nothing
```

---

# `Maybe (Maybe a)` の潰し方

```haskell
-- 二重の Maybe を一重に潰す
joinMaybe :: Maybe (Maybe a) -> Maybe a
joinMaybe Nothing         = Nothing
joinMaybe (Just Nothing)  = Nothing
joinMaybe (Just (Just x)) = Just x
```

```haskell
> joinMaybe Nothing
Nothing

> joinMaybe (Just Nothing)
Nothing

> joinMaybe (Just (Just 42))
Just 42
```

---

# `Either e`

---

# `Either e`

当日は飛ばします (`Maybe` とだいたい一緒なので)

```haskell
data Either e a
    = Left e
    | Right a

> :kind Either -- "Type ->" が1個多い
Either :: Type -> Type -> Type

> :kind forall e. Either e -- 👇ヨシ！
forall e. Either e :: Type -> Type
```

---

# `a -> Either e b` の繋げ方

- `Either e a` 型の値は**失敗する可能性がある**かつ**失敗の理由を返すことができる**計算だと思うことができる
  - `Left err` -> 計算は失敗して，その理由は `err` だった
  - `Right x`  -> 計算が成功して，値 `x` が得られた
- そのような計算を繋げて書きたい！💡

```haskell
-- どうやって"繋げる"？
f :: a -> (Either e) b
g :: b -> (Either e) c
```

```haskell
-- どうやって"潰す？"
joinEither :: Either e (Either e a)
```

---

# `a -> Either e b` の繋げ方

```haskell
instance Functor (Either e) where
    fmap
        :: (a -> b)
        -> (Either e) a
        -> (Either e) b
    fmap _ (Left  err) = Left err
    fmap f (Right x  ) = Right (f x)
```

```haskell
instance Applicative (Either e) where
    pure x        = Right x
    (Left  e) <*> _ = Left e
    (Right f) <*> r = fmap f r
```

```haskell
instance Monad (Either e) where
    (Left err) >>= _ = Left err
    (Right x ) >>= k = k x
```

---

# `Either e (Either e a)` の潰し方

```haskell
joinEither :: Either e (Either e a)
joinEither (Left err)         = Left err
joinEither (Right (Left err)) = Left err
joinEither (Right (Right x))  = Right x
```

---

# `[]`

---

# `[]`

0個または1個または2個または3個または…の値を持つ

```haskell
data [] a
    = []      -- nil
    | a : [a] -- cons
```

```haskell
> :kind [] -- 👇ヨシ！
[] :: Type -> Type
```

---

# `a -> [b]` の繋げ方

- `a -> [b]` は，**答えの候補が複数個ある**計算だと思うことができる
- そのような計算を繋げて書きたい💡

```haskell
-- どうやって"繋げる"？
f :: a -> [b]
g :: b -> [c]
```

```haskell
-- どうやって"潰す"？
joinList :: [[a]] -> a
```

---

# `a -> [b]` の繋げ方

```haskell
instance Functor [] where
    fmap
        :: (a -> b)
        -> ([a] -> [b])
    fmap f xs = map f xs
```

```haskell
instance Applicative [] where
    pure :: a -> [a]
    pure x = [x]

    (<*>)
        :: [a -> b]
        -> ([a] -> [b])
    fs <*> xs = concat (map (\f -> map (\x -> f x) xs) fs)
             -- [f x | f <- fs, x <- xs]
```

```haskell
instance Monad [] where
    (>>=) :: [a] -> (a -> [b]) -> [b]
    xs >>= k = concat (fmap k xs)
            -- concatMap k xs
```

---

# `a -> [b]` の繋げ方

```haskell
guard :: Bool -> [()]
guard True = [()]
guard False = []
```

`do` 構文を用いた例

```haskell
pythagorean :: [(Int, Int, Int)]
pythagorean = do
    y <- [1..]
    x <- [1..y]
    z <- [1..x+y]
    guard (x^2 + y^2 == z^2)
    pure (x, y, z)
```

```haskell
> take 10 pythagorean
[(3,4,5),(6,8,10),(5,12,13),(9,12,15),(8,15,17),(12,16,20),(15,20,25),(20,21,29),(7,24,25),(10,24,26)]
```

---

# `a -> [b]` の繋げ方

`pure` と `>>=` を使って書き直すと

```haskell
pythagorean' :: [(Int, Int, Int)]
pythagorean' =
    [1..] >>= (\y ->
        [1..y] >>= (\x ->
            [1..x+y] >>= (\z ->
                guard (x^2 + y^2 == z^2) >>= (\() ->
                        pure (x, y, z)
                    )
                )
            )
        )
```

---

# `a -> [b]` の繋げ方

`pure` と `>>=` を展開すると (わかりにくい……)

```haskell
pythagorean'' :: [(Int, Int, Int)]
pythagorean'' =
    concatMap
        (\y -> concatMap
            (\x -> concatMap
                (\z -> concatMap (\() -> [(x, y, z)]) (guard (x ^ 2 + y ^ 2 == z ^ 2))
                ) [1 .. x + y]
            ) [1 .. y]
        ) [1 ..]
```

---

# `[[a]]` の潰し方

```haskell
joinList :: [[a]] -> [a]
joinList = foldr (++) []
```

```haskell
> joinList [[0, 1, 2], [3, 4], [5, 6, 7]]
[0,1,2,3,4,5,6,7]
```

---

# `Reader r`

---

# `Reader r`

```haskell
-- 実態は r -> a という関数
newtype Reader r a = MkReader { runReader :: r -> a } -- r は後で与えられる
```

```haskell
> :type MkReader
MkReader :: (r -> a) -> Reader r a

> :type runReader
runReader :: Reader r a -> (r -> a)
```

---

# `a -> Reader r b` の繋げ方

- `Reader r a` (実質 `r -> a`)は，**あとで与えられる `r` 型の値を取ってこれる**計算だと思うことができる

```haskell
-- どうやって"繋げる"？
f :: a -> Reader r b -- 実質 a -> (r -> b)
g :: b -> Reader r c -- 実質 a -> (r -> c)
```

```haskell
-- どうやって"潰す"？
joinReader :: Reader r (Reader r a) -> Reader r a
```

---

# `a -> Reader r b` の繋げ方

```haskell
instance Functor (Reader r) where
    fmap
        :: (a -> b)
        -> (Reader r a) -- だいたい r -> a
        -> (Reader r b) -- だいたい r -> b
    fmap f (MkReader g) = MkReader (\r -> f (g x))
```

```haskell
instance Applicative (Reader r) where
    pure
        :: a
        -> (Reader r) a
    pure x = MkReader (\_e -> x)

    (<*>)
        :: (Reader r) (a -> b) -- だいたい r -> a -> b
        -> (Reader r) a        -- だいたい r -> a
        -> (Reader r) b        -- だいたい r -> b
    MkReader f <*> MkReader g = MkReader (\r -> f r (g r))
```

```haskell
instance Monad (Reader r) where
    (>>=)
        :: (Reader r) a        -- だいたい r -> a
        -> (a -> (Reader r) b) -- だいたい a -> r -> b
        -> Reader r b          -- だいたい r -> b
    MkReader f >>= k = MkReader (\r -> runReader (k (f r)) r)
```

---

# `a -> Reader r b` の繋げ方

`do` 構文を使った例

```haskell
-- Reader r (r 型の値が取れる) という環境の中で r 型の値を得る
ask :: (Reader r) r
ask = MkReader (\r -> r)

port :: Reader [(String, String)] (Maybe Int)
port = do
    env <- ask -- env :: [(String, String)]
    pure (lookupPortFromEnv env)
```

---

# `a -> Reader r b` の繋げ方

`pure` と `>>=` を使って書いたバージョン

```haskell
port' :: Reader [(String, String)] (Maybe Int)
port' =
    MkReader id >>= (\env ->
        pure (lookupPortFromEnv env)
    )
```

---

# `a -> Reader r b` の繋げ方

定義に即して書いたバージョン (読みづらい！！)

```haskell
port :: Reader [(String, String)] (Maybe Int)
port =
    MkReader (\r ->
        runReader (\env ->   -- この辺が >>= に対応
            MkReader (\_ ->  -- pure に対応
                lookupPortFromEnv env
            ) r
        ) r
    )
```

---
# `a -> Reader r b` の繋げ方

```haskell
-- Reader r (r 型の値が取れる) という環境の中で r' 型の値を得る
asks :: (r -> r') -> Reader r r'
asks f = MkReader f

-- Rational のリストを与えられたときに，平均値を求める
avg :: Reader [Rational] Rational
avg = do
    x <- asks sum
    y <- asks length
    let y' = fromInteger (toInteger y)
    pure (x / y')
```

---

# `Reader r (Reader r a)` の潰し方

`Reader r (Reader r a)` は実質 `r -> r -> a`
→ `r` を2回適用すればよさそう💡

```haskell
joinReader :: Reader r (Reader r a) -> Reader r a
joinReader (MkReader f) = MkReader (\r -> runReader (f r) r)
```

---

# `Identity`

---

# `Identity`

```haskell
-- 実質 a
data Identity a = Identity { runIdentity :: a }
```

- `a -> Identity b` は**何も付加的な情報が増えない"普通の"計算**であると思うことができる

---

# `a -> Identity b` の繋げ方

```haskell
instance Functor Identity where
    fmap f (Identity x) = Identity (f x)
```

```haskell
instance Applicative Identity where
    (Identity f) <*> (Identity x) = Identity (f x)
```

```haskell
instance Monad Identity where
    (Identity x) >>= f = f x
```

---

# `Identity (Identity a)` の潰し方

```haskell
joinIdentity :: Identity (Identity a) -> Identity a
joinIdentity (Identity (Identity x)) = Identity x
```

---

# 終わりに

---

# まとめ

- どういう性質を持てば monadic なのか？を個別のデータ型について確認した
  - 文脈が付与される計算が**繋げられる**
  - 二重に付与された文脈を一重に**潰せる**
- なぜモナドが重要か？
  - 前の**計算**に依存して，次の計算を決めることができる
  - 複数行の**プログラム**を書くために必要

# これから

- 今日見なかった他のデータ型についても，これからそのような見方をできるようになっていると👍
  - `State s`
  - `IO`
  - `MaybeT m`
  - 自分で定義するデータ型