ReactのuseEffectを悪用してBrainf*ckを実装する

# TL; DR

`useEffect` を意図的に無限ループさせてbrainf*ckインタープリターを走らせた

```ts
const [brain, setBrain] = useState(
    Brain.create("", new TextEncoder().encode(""))
);

useEffect(() => {
  setBrain((brain) => brain.next());
}, [brain]);
```

https://github.com/Syuparn/useeffect-brainf_ck

# はじめに

React初心者なので、よくうっかり `useEffect` で自身が参照しているstateを更新してしまい、無限ループを起こしてしまいます。ハングするUI、空費されるメモリ...

```ts
// レンダリング→useEffectでstate更新(=component更新)→レンダリング→useEffectで...
const [num, setNum] = useState(0);
useEffect(() => {
    setNum(num => num+1);
}, [num]);
```

そこで今回は、**悩みの種の無限ループを逆手にとって `useEffect` でbrainf*ckインタープリターを走らせてみました**。

# 作ったもの

https://github.com/Syuparn/useeffect-brainf_ck

![sceenshot.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/239887/03fc2227-61ae-1251-eaf3-dd6a3b66aa72.png)

`source` にソースコードを入れると実行結果が `result` に出力されます。見た目は普通のUIインタープリターと変わりありません。

# 動作原理

仕組みは冒頭の無限ループのコードと同じです。インタープリターの状態をオブジェクト化し、**useEffectの中で「現在のトークンを評価した直後の状態」をセット**します。

```ts
useEffect(() => {
  setBrain((brain) => brain.next());
}, [brain]);
```

無限ループによって、結果的にソースコードのトークンを先頭から末尾まで評価した後の状態が得られます[^loop]。

useEffectの動作の仕組みについては、以下の記事が非常に参考になりました。

https://qiita.com/honey32/items/62edf5165aced7d0c4bf

インタープリターの状態は、以下のオブジェクトで管理しています。入出力、メモリ、ソースコードとそれぞれのどの場所を今読んでいるかを保持しています。

```ts
export class Brain {
  private constructor(
    public readonly input: Uint8Array,
    public readonly inputCursor: number,
    public readonly output: Uint8Array,
    public readonly memory: Uint8Array,
    public readonly memoryCursor: number,
    public readonly source: string,
    public readonly sourceCursor: number
  ) {}

  public next(): Brain { // ソースコード上の次のトークンを評価
    if (this.sourceCursor >= this.source.length) {
      return this;
    }

    switch (this.source[this.sourceCursor]) {
      case "+":
        return this.add();
      case "-":
        return this.sub();
      case ">":
        return this.inc();
      case "<":
        return this.dec();
      case ",":
        return this.read();
      case ".":
        return this.write();
      case "[":
        return this.loop();
      case "]":
        return this.break();
    }

    return this.comment();
  }

  private add(): Brain { // +：今参照しているポインタの値をインクリメント
    return new Brain(
      this.input,
      this.inputCursor,
      this.output,
      updateUint8Array(this.memory, this.memoryCursor, (e) => e + 1),
      this.memoryCursor,
      this.source,
      this.sourceCursor + 1
    );
  }
}
// その他のメソッド...
```

特徴としては、内部の状態を変化させる代わりに **「評価後の状態」を新たに生成** して返しています。状態変化を `useEffect` に任せられるためこのような実装になりましたが、結果的にループも破壊的変更も無いメソッドになりました。~~Reactは、純粋関数型Brainf*ck処理系を実装するのにうってつけです~~

# いろいろなソースコードを試してみる

せっかく作ったので色々なbrainf*ckソースコードを実行してみます。
こちらの記事でいろいろなサンプルコードが紹介されていたので一通り試してみました。

https://hirlab.net/nblog/category/programming/art_1604/

想定通りの結果が得られたので、インタープリターに目立ったバグは無さそうです。

![square.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/239887/8b3ee091-5013-8bec-9f51-10300cacd3c0.png)

とはいえ、レンダリングをステップ数分実行しているので、**実行にはかなり時間がかかりました**。
（上の例だと15万回ステップ、表示が終わるまで**3分程度**かかった）
1ステップずつ実行されるので、1文字ずつ出力される様子が目視できます。気長に待ちましょう。

# おわりに

しょうもない記事に最後までお付き合いいただきありがとうございました。

今回の方法は、仕組み上言語処理系以外でもループが必要なアルゴリズムならなんでも使えます、ソートなりニュートン法なりお好きなものを `useEffect` で計算しましょう。 ~~実行速度とチームからの微妙な視線については責任を負いかねますのでご了承ください。~~

（おまけ：過去の文法悪用シリーズ）

https://qiita.com/Syuparn/items/d88f7da6c010cff04f86

https://qiita.com/Syuparn/items/21d06eebbe2eb883bf31

https://qiita.com/Syuparn/items/77935b5a4af046fc6fa9

https://qiita.com/Syuparn/items/1afa97e8dd731923f1ad

https://qiita.com/Syuparn/items/2072207ec0565a80d2b2

[^loop]: ソースコードを読み終わると `brain` の状態が変わらなくなるため、無限ループは停止します。