コンマ演算子を駆逐するイディオムを思いついた

# コンマ演算子による順次評価

`expr1, expr2, ..., exprN` のようなコンマ演算子を使った式（引数リストじゃないよ）は左から右に、すなわち `expr1` から順に評価され、`exprN` を評価した結果が式全体の評価結果となります。イディオム的に使われますが、邪悪です。

その邪悪さゆえにこのような演算子を用意していない言語もあるらしく、D 言語でも言語仕様から抹殺しようという動きがあったこともあります。

# D 言語教の 1 宗派

唐突ですが。

- [D言語のUFCSが好きだ！](http://qiita.com/nak2yoshi/items/1bbd91fb23d7cc67109e)

自分も好きです。UFCS チェインで書くのが生きがいです。また、`import` 宣言以外にセミコロンを複数回使ったら負けかなと思っているワンライナー派です。

# ダサいワンライナー

## 普通の例

こういう処理があったとします。

```d
auto add1(int n)
{
    return n + 1;
}

import std.stdio;

void main()
{
    int n = -1;
    writeln(++n);
    n = 3;
    writeln(n);
    n *= 2;
    writeln(n);
    auto r = add1(n + 999);
}
```

次のような出力結果が期待されます。

```
0
3
6
```

`main` 関数にはセミコロンが 7 つもあるので大敗です。

## コンマ演算子の例

`main` 関数の中は単純な式を順々に評価していくだけなので、コンマ演算子を使って次のように書けます。

```d
int n = -1;
auto r = (writeln(++n), n = 3, writeln(n), n *= 2, writeln(n), add1(n + 999));

```

セミコロンが 2 つに減りました。とてもダサい上に、負けです。さらにラムダを使って次のように書きます。

```d
auto r = (n => (writeln(++n), n = 3, writeln(n), n *= 2, writeln(n), add1(n + 999)))(-1);
```

ラムダに引数として `-1` を直接与え、即時に呼び出しています。変数宣言を消したいときによく使っています。これ自体は今回の目的からは脱線しますが。セミコロンは 1 つだけですが、まだダサいです。

コンマ演算子は大変よいものなのですが、優先順位が低いので式全体を括弧でくくる必要があり、見た目も引数リストみたいでなんか許せません。ただセミコロンをコンマに置き換えただけの、ワンライナーの風上にも置けない代物として扱います。

# Effective T

## `std.range.tee`

`std.range.tee` という**レンジ処理用の関数**があります。以前、この [`tee` の解説](http://qiita.com/e10s/items/43714e6217f97402a94b#stdrangetee)を書いたので見てほしいのですが、ちょっとトリッキーな関数です。

## `tee` がコンマ演算子を駆逐する

これこそが今回紹介したいイディオムです。

```d
import std.range;

int n = -1;
auto r = [() => add1(n + 999)]  // 最終的な評価値を返すラムダのみからなる
    .tee!(_ => writeln(++n), No.pipeOnPop)
    .tee!(_ => n = 3,        No.pipeOnPop)
    .tee!(_ => writeln(n),   No.pipeOnPop)
    .tee!(_ => n *= 2,       No.pipeOnPop)
    .tee!(_ => writeln(n),   No.pipeOnPop)
    .front()();  // front はラムダを返すプロパティ（関数）
```

何なんだこれは。`tee` だらけの UFCS チェインですよ。もちろん、コンマ演算子バージョンのように書き直せば大勝利です。

```d
auto r = (n =>
    [() => add1(n + 999)]
    .tee!(_ => writeln(++n), No.pipeOnPop)
    .tee!(_ => n = 3,        No.pipeOnPop)
    .tee!(_ => writeln(n),   No.pipeOnPop)
    .tee!(_ => n *= 2,       No.pipeOnPop)
    .tee!(_ => writeln(n),   No.pipeOnPop)
    .front()()
)(-1);
```

意図的に改行していますが、ワンライナーと呼べる具合になりました。宗教上の理由でまた脱線しました。

回したいレンジは `[() => add1(n + 999)]` で、これは最終的に評価したい式 `add1(n + 999)` を評価して返す引数なしラムダのみからなる配列です。

## `front()()`?

ここにおけるすべての `tee` は `[() => add1(n + 999)]` というレンジを受け取り、そのレンジをラップしたレンジを返します。それゆえ、その返されるレンジの `front` プロパティは、ラムダ `() => add1(n + 999)` を返します。

実のところ `front` プロパティの正体は関数なので、括弧付きで呼び出しても同じことです（`@propety` 付きなのでプロパティ記法を使って括弧を省略して呼び出すべきですが）。その戻り値である引数なしラムダを呼び出す（`(() => add1(n + 999))()` 相当のことをする）ために、`front()()` という妙なことになっています。この辺はコンパイラの挙動とのすりあわせなので本質的なところではないのですが、ちょっと注意が必要です。

ただ、**最後に評価したい式（`add1(n + 999)` の部分）は定数、たとえば `constN` だという場合は、`[() => constN]` ではなく `[constN]` のようにできるので、そのときは `()()` は不要です**。 

## `tee!(fun, No.pipeOnPop)(range)`?

`tee!(fun, No.pipeOnPop)(range)` に渡したテンプレート引数 `fun` は `range` の要素を引数としてとる関数です。今回の例でその引数として渡されるのは `() => add1(n + 999)` ですが、ここにおいてはダミー扱いです。アンタッチャブルな産廃なので呼び出さないでください。。

では `tee` に与えたラムダはいつ呼ばれるのかというのが気になります。実は先述の解説記事で検証しているわけですが、テンプレート引数の `No.pipeOnPop` が肝です。

実際、これらの `tee` に与えたラムダは（この書き方では）上から順に呼ばれます。そして最後に `() => add1(n + 999)` が呼び出され、全体として目的の処理が達成されるのです。

注意として、**`No.pipeOnPop` を忘れるとデフォルト引数の `Yes.pipeOnPop` が使用され、結果としてその `tee` に与えたラムダは呼び出されません**。

## 注意点を踏まえたバリエーション

```d
[true]
.tee!(_ => hoge(), No.pipeOnPop)
.tee!(_ => assert(0))  // Yes.pipeOnPop な tee なので到達しない
.tee!(_ => fuga(), No.pipeOnPop)
.front;  // ()() 不要
```

# コンマ演算子を駆逐しつつも順次評価するイディオム

以上のことを一般化すると、コンマ演算子を使った式 `expr1, expr2, ..., exprN` は `std.range.tee` を濫用することで（あらゆる場面でかはわからないものの）置換可能です。

```d
import std.range;

[() => exprN]
.tee!(_ => expr1, No.pipeOnPop)
.tee!(_ => expr2, No.pipeOnPop)
................
.front()()
```

コンマな式の最右辺が定数 `constN` であり、`expr1, expr2, ..., constN` のような形であれば、構文糖衣が使えます。

```d
[constN]
.tee!(_ => expr1, No.pipeOnPop)
.tee!(_ => expr2, No.pipeOnPop)
................
.front
```

# 感想

そういうテンプレート関数を `std.functional` あたりにぶち込むのが得策だと思います。

# 謝辞

このハックは ueshita 先生謹製の dl コマンドのワンライナー化ハックをしているときに思いつきました。ありがとうございます。

- [lsを間違えてdlと入力してしまったときに、D言語くんが通り過ぎるコマンド](http://qiita.com/ueshita/items/06befb8790736c7294ae)

せっかくなので、`tee` を躍動させることで高度なレンジ化に成功した dl コマンドを置いておきますね。バグってたらごめんなさい。

- https://gist.github.com/e10s/5e3ec3be72f9fbd9c458