Freer Effectsが、だいたいわかった: 11-9 LambdaCase拡張

Freer Effectsが、だいたいわかった: 11-9 LambdaCase拡張
===================================================

はじめに
-------

LambdaCase拡張は、ちょっとした構文上の拡張なのだけど、これを使うことでコードがきれいになる。「どのようにきれいになるか」を「Erlangのプロセスを使ったコードをHaskellで書きなおす例」で説明する。

目次
----

(0). [導入](https://qiita.com/YoshikuniJujo/items/c71644b5af1f5195cbf3)

1. [Freeモナドの概要](https://qiita.com/YoshikuniJujo/items/988ac4b69a27974154fd)
	* Freeモナドとは
	* FreeモナドでReaderモナド、Writerモナドを構成する
2. [存在型(ExistentialQuantification拡張)の解説](
	https://qiita.com/YoshikuniJujo/items/988ac4b69a27974154fd )
3. [型シノニム族(TypeFamilies拡張)の解説](https://qiita.com/YoshikuniJujo/items/17853e423c39409d8dfe)
4. [データ族(TypeFamilies拡張)の解説](https://qiita.com/YoshikuniJujo/items/b3c442d0387a35bfe860)
5. [一般化代数データ型(GADTs拡張)の解説](https://qiita.com/YoshikuniJujo/items/739a1e3abed8c602dba7)
6. [ランクN多相(RankNTypes拡張)の解説](https://qiita.com/YoshikuniJujo/items/4094b5fe5a7a33f3d1e6)
7. [FreeモナドとCoyoneda](https://qiita.com/YoshikuniJujo/items/729ad9830833eacb9d75)
	* Coyonedaを使ってみる
	* FreeモナドとCoyonedaを組み合わせる
		+ いろいろなモナドを構成する
8. [Freerモナド(Operationalモナド)でいろいろなモナドを構成する](
	https://qiita.com/YoshikuniJujo/items/686fedc92fd20ff70ab8 )
	* FreeモナドとCoyonedaをまとめて、Freerモナドとする
	* Readerモナド
	* Writerモナド
	* 状態モナド
	* エラーモナド
9. [モナドを混ぜ合わせる(閉じた型で)](
	https://qiita.com/YoshikuniJujo/items/19a6e9dada698a5ebfb6 )
	* Freerモナドで、状態モナドとエラーモナドを混ぜ合わせる
		+ 両方のモナドを一度に処理する
		+ それぞれのモナドを、それぞれに処理する
10. [存在型による拡張可能なデータ構造(Open Union)](
	https://qiita.com/YoshikuniJujo/items/8dd63c9415ccda20be28 )
11. 追加の言語拡張
	1. [ScopedTypeVariables拡張](
		https://qiita.com/YoshikuniJujo/items/103807ee6692e8c2c48b )
	2. [TypeOperators拡張](
		https://qiita.com/YoshikuniJujo/items/68af70347e61849ccea9 )
	3. [KindSignatures拡張](
		https://qiita.com/YoshikuniJujo/items/0f581acc78d2ba8e3a7c )
	4. [DataKinds拡張](
		https://qiita.com/YoshikuniJujo/items/c06c01f9a2d344ac211f )
	5. [MultiParamTypeClasses拡張](
		https://qiita.com/YoshikuniJujo/items/165460f39f1cae751300 )
	6. [FlexibleInstances拡張] (
		https://qiita.com/YoshikuniJujo/items/eb70e7978f333ef3b514 )
	7. [OVERLAPSプラグマ](https://qiita.com/YoshikuniJujo/items/6b57a2778b04f54cac1e)
	8. [FlexibleContexts拡張](https://qiita.com/YoshikuniJujo/items/a7146d041f88ba00d574)
	9. LambdaCase拡張
12. [Open Unionを型によって安全にする](https://qiita.com/YoshikuniJujo/items/3e7adbbbf0e7f73f669f)
13. モナドを混ぜ合わせる(開いた型で)
	* FreeモナドとOpen Unionを組み合わせる
	* 状態モナドにエラーモナドを追加する
14. Freer Effectsで、IOモナドなどの、既存のモナドを使用する
15. 関数を保管しておくデータ構造による効率化
16. いろいろなEffect
	* 関数handleRelayなどを作成する
	* NonDetについてなど

コード例
-------

GitHubにコード例を置いておきます。

[GitHub: YoshikuniJujo/test_haskell/tribial/qiita/try-lambda-case](https://github.com/YoshikuniJujo/test_haskell/tree/master/tribial/qiita/try-lambda-case)

Erlangの軽量プロセスの例
----------------------

Erlangについての説明は、WikipediaやMatz氏の記事を参照してください。

[Wikipedia: Erlang](https://ja.wikipedia.org/wiki/Erlang)
[Rubyistのための他言語訪問](https://magazine.rubyist.net/articles/0017/0017-Legwork.html)

上記の記事中の「軽量プロセスを使ったコード例」を紹介する。

```erlang:pingpong.erl
-module(pingpong).
-export([start/0, ping/2, pong/0].

ping(0, Pong_PID) ->
        Pong_PID ! finished,
        io:format("Ping finished\n", []);
ping(N, Pong_PID) ->
        Pong_PID ! {ping, self()},
        receive
                pong -> io:format("Ping received pong\n", [])
        end,
        ping(N - 1, Pong_PID).

pong() ->
        receive
                finished ->
                        io:format("Pong finished\n", []);
                {ping, Ping_PID} ->
                        io:format("Pong received ping\n", []),
                        Ping_PID ! pong,
                        pong()
        end.

start() ->
        Pong_PID = spawn(pingpong, pong, []),
        spawn(pingpong, ping, [3, Pong_PID]).
```

上記のようにソースファイルpingpong.erlを作成して、つぎのように読み込み、実行する。

```
% erl
1> c(pingpong).
2> pingpong:start().
Pong received ping
Ping received pong
Pong received ping
Ping recieved pong
Pong received ping
Ping received pong
ping finished
Pong finished
```

関数pingは「回数N」と「pongを受け取りpingを送るプロセスのID」であるPong_PID」を受けとると、N回「pingを送りpongを受けとる」。N回の送受信が終了したらfinishedを送る。関数pongはpingを受けとったら、送信元のプロセスにpongを送りかえしループする。finishedを受けとったら終了する。

Haskellでのpingpongの実装
------------------------

これとおなじものをHaskellで実装してみよう。ソースコードを示す。

```haskell:src/PingPong.hs
{-# OPTIONS_GHC -Wall -fno-warn-tabs #-}

module PingPong where

import Control.Concurrent
import Control.Monad.STM
import Control.Concurrent.STM.TChan

data Ping = Ping (TChan Pong) | Finished
data Pong = Pong

ping :: TChan Pong -> Int -> TChan Ping -> IO ()
ping _self n pon | n < 1 = do
        atomically $ writeTChan pon Finished
        putStrLn "Ping finished"
ping self n pon = do
        atomically $ writeTChan pon (Ping self)
        r <- atomically (readTChan self)
        case r of
                Pong -> putStrLn "Ping received pong"
        ping self (n - 1) pon

pong :: TChan Ping -> IO ()
pong self = do
        r <- atomically (readTChan self)
        case r of
                Finished -> putStrLn "Pong finished"
                Ping pin -> do
                        putStrLn "Pong received ping"
                        atomically $ writeTChan pin Pong
                        pong self

start :: IO ()
start = do
        pin <- newTChanIO
        pon <- newTChanIO
        _ <- forkIO $ pong pon
        ping pin 3 pon
```

これを対話環境で実行する。

```
*PingPong> start
Pong received ping
Ping received pong
Pong received ping
Ping received pong
Pong received ping
Ping received pong
piPnogn gf ifniinsihsehde
d
```

最後のfinishedのメッセージはPingとPongとでまざってしまっているが、これはプロセスが並行して走っていて、Ping側がfinishedのメッセージを表示するのに、Pong側の応答を待たないからだ。

Erlangでは、それぞれのプロセスがはじめから「受信箱」を用意されているがHaskellでは、明示的に用意してあげる必要がある。ここではTChanというSTMを利用するチャンネルを使用した。関数atomicallyについては、ここでは説明しない。

TChanを使うことで、writeTChanで書き込んだものが、readTChanから読み込める。これによってプロセス間でのデータのやりとりを実現している。

Haskellでも(Erlangでいうところの)軽量プロセスによるpingpongができた
--------------------------------------------------------------

すばらしい。TChanを使うことで、Erlangでプロセスがメッセージを送りあうのと同等のことができる。いいね。

ただ、すこし不満がある。Erlangでメッセージを受け取っている部分をみてみよう。

```erlang
        receive
                finished ->
                        io:format("Pong finished\n", []);
                {ping, Ping_PID} ->
                        io:format("Pong received ping\n", []),
                        Ping_PID ! pong,
                        pong()
        end.
```

すばらしい。受け取ったメッセージをそのままパターンにマッチさせている。ムダがなくてきれいな文法だ。Haskellでの、おなじ部分をみてみよう。

```haskell
        r <- atomically (readTChan self)
        case r of
                Finished -> putStrLn "Pong finished"
                Ping pin -> do
                        putStrLn "Pong received ping"
                        atomically $ writeTChan pin Pong
                        pong self
```

readTChanの結果で、いちど変数rを束縛している。それをcase文にわたすことで、パターンマッチしている。ムダだ。こういうムダに変数を使うのは美しくない。すこし気持ち悪い。

LambdaCase拡張を使う
-------------------

そこで、LambdaCase拡張ですよ。ちょっとした話なのだけど、コードをきれいにする気の効いた拡張だ。LambdaCase拡張を使って書き直してみよう。

```haskell
pong self = atomically (readTChan self) >>= \case
        Finished -> putStrLn "Pong finished"
        Ping pin -> do
                putStrLn "Pong received ping"
                atomically $ writeTChan pin Pong
                pong self
```

ムダな変数rがなくなり、すっきりした。一般的には、つぎのようになる。

```haskell
\x -> case x of ...
```

```haskell
\case ...
```

変数xをとり、それをcase文を使ってパターンマッチするような関数を、\のあとに予約語caseを置くことで、スマートに記述することができる。

最終的なソースコード
------------------

最終的なソースコードを示す。

```haskell:src/PingPongLambdaCase.hs
{-# LANGUAGE LambdaCase #-}
{-# OPTIONS_GHC -Wall -fno-warn-tabs #-}

data Ping = Ping (TChan Pong) | Finished
data Pong = Pong

ping :: TChan Pong -> Int -> TChan Ping -> IO ()
ping _self n pon | n < 1 = do
        atomically $ writeTChan pon Finished
        putStrLn "Ping finished"
ping self n pon = do
        atomically $ writeTChan pon (Ping self)
        atomically (readTChan self) >>= \case
                Pong -> putStrLn "Ping received pong"
        ping self (n - 1) pon

pong :: TChan Ping -> IO ()
pong self = atomically (readTChan self) >>= \case
        Finished -> putStrLn "Pong finished"
        Ping pin -> do
                putStrLn "Pong received ping"
                atomically $ writeTChan pin Pong
                pong self

start :: IO ()
start = do
        pin <- newTChanIO
        pon <- newTChanIO
        _ <- forkIO $ pong pon
        ping pin 3 pon
```

まとめ
-----

Erlangでプロセスがメッセージを受けとる構文では、メッセージで変数を束縛することなく、直接パターンにマッチさせることができる。ムダのないきれいな文法だ。HaskellではLambdaCase拡張を使うことで、おなじような、きれいな書きかたができる。