TypeScriptでif文を式として実装する

# モチベーション
ミュータブルな値の管理は参照されている箇所やその値の状態に意識を向けねばならず、テスタビリティの低下や認知負荷の向上を招きやすいです。そのためできることならミュータブルな実装は避けたほうが安全です。

TypeScriptではif/elseもtry/catchも、どちらも文なので評価して値を返すことはできません。そこでif/elseを式として実装し、式として評価することで処理の結果をイミュータブルに扱えるようにしてみます。

# 実装と使い方
## if式を定義する
条件と、条件がtrueの場合に実行したい無名関数を渡すと状態を持つ `Evaluator` を返す `If` 関数を定義します。if式はifでfalseとなっても何がしかの値を、最初に渡した無名関数と同じ型の値を返す必要があり、 `Evaluator` を返すことが式として評価されるための制約にもなりえます（elseの指定も矯正されるということです）。

```typescript
/**
 * 条件がtrueのときに実行する関数の型定義(エイリアス)
 */
type BasicExpression<T> = () => T;

export const If = <T>(condition: boolean, block: BasicExpression<T>)
: Evaluator<T> => {
  return new Evaluator<T>(condition, block);
};
```

この実装例ではやたらと `() => T` の型が登場するのでエイリアスとしてtypeを作っておきます（なくてもいいけど、あったほうがちょっと読みやすくなるくらい）。
```typescript
type BasicExpression<T> = () => T;
```

 `elseIf` ないしは `else` が呼ばれた際、条件と条件がtrueのときに処理する関数のペアを配列に持っておきます。評価順序が大事なのですが、内部的にはキューのように最後尾に積んでおきます。

```typescript
type ConditionFunctionPair<T> = [boolean, BasicExpression<T>];

private readonly condtionFunctions: ConditionFunctionPair<T>[]
```

`ConditionFunctionPair` は条件式とtrueの場合に実行したい関数のペアとなる型エイリアスです。


`If` を式として評価するので `If ` 単体で使うことは想定しません。 `else` まで呼ばれて評価される前提で、elseまで呼ばれた際に `condtionFunctions` に積んだ条件を一個ずつ評価・実行します。 クラス内部では `condtionFunctions` はタプルの配列として扱われています。 `If` 同様、 `elseIf` も `else` も関数です。
```typescript
else(alternative: BasicExpression<T>): T {
  this.condtionFunctions.push([true, alternative]);
  for (const pair of this.condtionFunctions) {
      if (pair[0]) {
        return pair[1]();
      }
  }
  throw new Error('All conditions was not matched');
}
```

`else` まで到達したら、一個ずつ条件を評価していくので `elseIf` は condtionFunctions に条件と条件がtrueの際に実行する関数のタプルを積んで状態のみを返します。

```typescript
elseIf(condition: boolean, block: BasicExpression<T>): Evaluator<T> {
  this.condtionFunctions.push([condition, block]);
  return this;
}
```

`Evaluator` は`If` が呼ばれたあとの状態と評価順序を定義するクラスです。概念的にはビルダパターンのような構造になっています。ここまでをまとめると以下のようになります。

```typescript
/**
 * 条件と式の組み合わせを状態として保持し、評価の順番を定義する
 */
class Evaluator<T> {
  constructor(
      condition: boolean,
      block: BasicExpression<T>,
  ) {
    this.condtionFunctions.push([condition, block]);
  }

  private readonly condtionFunctions: ConditionFunctionPair<T>[] = [];

  elseIf(condition: boolean, block: BasicExpression<T>): Evaluator<T> {
    this.condtionFunctions.push([condition, block]);
    return this;
  }

  else(alternative: BasicExpression<T>): T {
    this.condtionFunctions.push([true, alternative]);
    for (const pair of this.condtionFunctions) {
      if (pair[0]) {
        return pair[1]();
      }
    }
    throw new Error('All conditions was not matched');
  }
}
```

## 使い方
if文と同じです。ただし、式として評価できるので結果を変数にとっておくことができます。
```typescript
const base = "The quick brown fox jumps over the lazy dog.";

const result = If(base.length > 30, () => 'over 30')
            .else(() => 'under 30')
```

elseIf を挟む場合も同様です。上記の例とは別の例です。 elseIf は状態を更新する中間操作なので複数重ねられます。たとえば、なんかしらステータスを返すロジックがあるとして...（ここではモックとして固定値を返すものとします）。
```typescript
class StatusFindService {
  execute(): string {
    return '5';
  }
}
```

下記のコードでは2番目に評価される `result === '5', () => ({message: 'not found'})` が結果になります。
```typescript
const result = new StatusFindService().execute();

const res = If(result === '0', () => ({message: 'success'}))
    .elseIf(result === '5', () => ({message: 'not found'}))
    .else(() => ({message: 'failure'}));
```

したがって `res` は `{message: 'not found'}` になります。その他、elseIfはelseが呼ばれるまで評価されないためいくつか重ねることができます。


# 参考
ソースコードは [expr.ts](https://github.com/simonNozaki/expr.ts) にあります。

また、こちらの記事は大変参考にさせていただきました: https://qiita.com/hatakoya/items/018afbfb1bd45136618a

## おまけ
Kotlinの `Result` 、scala.util.Try を参考にした [resultify](https://www.npmjs.com/package/@snozaki/resultify) を作っています。