パイプの前のプロセスが成功したときだけ処理したい(Maybeっぽいパイプ)

[この記事](https://dekokun.github.io/posts/2012-08-05.html)の続きがなさそうだったので勝手に書く。
(この記事はshのパイプをmaybeモナドに見立てるにはどうするかという話だ。)

前の処理が成功したときだけ処理したい場合、shだと以下のように書く。

```bash
command1 && command2 && command3 && ...
```
ただこれだと処理結果を後ろに渡せない。一方パイプの処理は戻り値を参照しない。

```bash
command1 | command2 | command3 | ...
```
したがって以下を全部満たすようなケースではパイプは使用できない。

* 異常復帰する場合も標準出力に出力する場合がある
* 以下のような理由でコマンドが異常復帰する場合は、処理したくない
    * 出力が信用できない場合
    * 出力が行志向ではなく、不完全になる場合
    * ロールバックがめんどくさい、などなど

標準出力は処理中に得られるが、戻り値は処理後に得られるのでこれはどうしようもない。
上記のような場合は以下のように処理することになるだろう。

```bash
command1 > file1         &&
command2 < file1 > file2 &&
command3 < file2 > file3 &&
...
```
実際にはファイル(または変数)の名前をいちいち考えないといけないし、コピペでファイルを取り違えると面倒なことになる。
こう書けると嬉しい(ことにする)。

```
may command1    |
  fmap command2 |
  fmap command3 |
  ...
  cmd
```
ここで

* mayは引数のコマンドから標準出力と戻り値を取り出して、出力をmaybe的文脈付きに変換する関数
* fmapは文脈付き標準入力を通常の標準入力に変換してコマンドに渡し、標準出力に再度文脈を付加して出力する関数
* cmdは特殊な文脈付き標準入力を通常のコマンドの文脈(標準出力+戻り値)に変換する関数

fmapとcmdは型に応じてディスパッチさせたいので、入力には型情報が必要である。
標準入出力に流す文脈付きデータ構造は以下のような感じにする。

```
<型情報>
<値> ...
いつもの出力
...
```
例)

```
$ may echo a                 # コマンド -> maybe(成功)への変換
maybe                        <--型情報
0                            <--戻り値(成功)
a                            <--データ
$ may false                  # コマンド -> maybe(失敗)への変換
maybe                        <--型情報
1                            <--戻り値(失敗)
$ may echo a | cmd           # maybe(成功) -> コマンドへの変換
a                            <--データ
$ may false | cmd            # maybe(失敗) -> コマンドへの変換
$                            # 値は得られない
$ echo $?
1                            <--戻り値がリストアされる
$ may echo x | fmap grep x
maybe                        <--型情報
0                            <--戻り値(成功)
a                            <--データ
$ may false | fmap grep x
maybe                        <--型情報
1                            <--戻り値(失敗)
```

実装例)

```bash
may() {
  : `mktemp`
  { rm $_
    "$@" >/dev/fd/3 && just cat /dev/fd/3 || nothing $?
  } 3<>$_
}

just() {
  echo maybe
  echo 0
  "$@"
  return 0
}

nothing() {
  echo maybe
  echo ${1:-1}
  return ${1:-1}
}

monad_dispatcher() {
  local type value f=$1
  shift
  read type
  ${f}_$type "$@"
}

alias fmap='monad_dispatcher fmap'
alias cmd='monad_dispatcher cmd'

fmap_maybe() {
  bind_maybe may "$@"
}

bind_maybe() {
  local value
  read value
  case "$value"
  in 0) "$@"
  ;; *) nothing "$value"
  esac
}

cmd_maybe() {
  local value
  read value
  case "$value" in 0)
    cat -
  esac
  return "$value"
}
```

性能上の配慮から引数のコマンドの復帰値を気にしないfmap_を定義しよう。

```bash
alias fmap_='monad_dispatcher fmap_'
fmap__maybe() {
  bind_maybe just "$@"
}
```

```
$ may : | fmap grep x
maybe
1
$ may : | fmap_ grep x
maybe
0
```

## 少し複雑な例
すごいH本の[あれ](http://learnyouahaskell.com/a-fistful-of-monads#walk-the-line)をやろう。

```bash
landleft() {
  local left right
  read left right
  left=$((left+$1))
  if diff_lt $left $right 4; then 
    just echo $left $right
  else
    nothing
  fi
}

landright() {
  local left right
  read left right
  right=$((right+$1))
  if diff_lt $left $right 4; then 
    just echo $left $right
  else
    nothing
  fi
}

banana() {
  nothing
}

diff_lt() {
  if [ $1 -gt $2 ]; then
    [ $(($1-$2)) -lt $3 ]
  else
    [ $(($2-$1)) -lt $3 ]
  fi
}

alias bind='monad_dispatcher bind'
```
ここでは組み合わせる関数がすでにmaybe的なものなので、fmapではなくbindを使う。

```
$ just echo 0 0 | bind landleft 4
maybe
1      <--- 失敗!
$ just echo 0 0 | bind landleft 2 | bind landright 2 |  bind landleft 2
maybe
0      <--- 成功!
4 2
$ just echo 0 0 | bind landleft 1 | bind banana |  bind landleft 1
maybe
1      <--- 失敗!
```
### 余談
just関数はhaskellのjustには対応せず、正確には just cat - が対応物になるが、無駄にフォークすることになるので今の実装にした。

また、maybeの文脈と通常のコマンドの文脈を行き来したかったので、nothingが値(失敗したときの戻り値)を持つようになっており、either型の方が近いかもしれない。

monad則の対応物は恐らく以下だと思っているが、よくわからない。

```:元
return x >>= f ⇔ f x
m >>= return ⇔ m
(m >>= f) >>= g ⇔ m >>= (\x -> f x >>= g)
```
```:対応物
just cat | bind f ⇔ f
m | bind just cat ⇔ m
{ m | bind f; } | bind g ⇔ m | bind eval 'f | bind g'
```