なぜ我々は頑なにforを避けるのか

## 動機

[前回の記事](https://qiita.com/tetsuya-zama/items/7e55d1ffbbfb4170cac7)を投稿したことを某SNSで通知したところ、そのSNSでこんなコメントをいただいた。転記する許可を取ったわけでは無いので私なりに要約させていただくと、

> なぜそんなトリッキーな書き方をしてまでforを使うのを避けるのか
> そんな書き方をして可読性を下げるくらいなら素直にforを使う方が良い

ということだと理解している。

なるほど、一理ありそうだ。しかし一方で、前回貼った[StackOverflow](https://stackoverflow.com/questions/3746725/how-to-create-an-array-containing-1-n)のQ&Aはなかなかの人気記事（質問に1243ポイント、回答に最大で1559ポイント）なので「多少トリッキーなことをしてでもforを書きたくない！！」という意見をもつプログラマも一定以上いるのだろう。当然私もその1人だ。

ということで、この記事で「なぜそこまで意固地になってまでforを書きたくないのか」を説明することにする。

尚、今回は前回の記事つながりで言語はJavaScriptを使うが、基本的にはどの言語でも同じだと思って欲しい。

## 理由１　そもそもforの構文って結構複雑

こんな配列があるとする。

```javascript
const suites = ["H","C","D","S"]; //H:ハート C:クローバー D:ダイア S：スペード
const nums = ["A","2","3","4","5","6","7","8","9","T","J","Q","K"]; //トランプの数字
```

この配列を元にジョーカーを除いたトランプ52枚すべてを標準出力(`console.log`)したい。
例えば、ハートの２なら"H:2"、スペードのキングなら"S:K"と出力する。

`for`を使って素直に書くならこんな感じだろうか。

```javascript
for(let i = 0; i < suites.length; i++){
  for(let j = 0; j < nums.length; j++){
    console.log(suites[i] + ":" + nums[j]);
  }
}
```

初学者だった時やしばらくプログラミングから離れて久々に書いた時のことを思い出してほしい。

> あれ、`i < suites.length`でいいんだっけ？ `i < suites.length - 1`だっけ？
> それとも、`i <= suites.length`だっけ??

こんなこと悩んだ経験はないだろうか？

初学者だけではなく、ベテランであっても仕事が立て込んでいて疲れていれば`i <= suites.length`と誤って書いてしまうかも知れない。
貴方はソースレビューでこのバグを発見できる自信があるだろうか？テストフェーズで発見できれば良いが、うまく境界値がテストパターンからすり抜けてしまったら...?

一方で、`for`を避けて`Array#forEach`で実装した例を見てみよう。

```javascript
suites.forEach(suite => nums.forEach(num => console.log(suite + ":" + num)));
```

どうだろう？少なくともforEachの文法を覚えていれば、`<`か`<=`かといった問題や`length - 1`かどうかといった問題に悩まされることも疲れていて間違えてしまうことも無い。ソースレビューで誰がどう読んだって`suites`の要素数と`nums`の要素数分ループするのは明らかである。

前者の例と後者の例、どちらが***安全***だろうか？
`for`とは本質的に***バグりやすい複雑な構文***なのである。

## 理由２ 配列に添字でアクセスしたくない

同じサンプルを再掲する。

```javascript
const suites = ["H","C","D","S"]; //H:ハート C:クローバー D:ダイア S：スペード
const nums = ["A","2","3","4","5","6","7","8","9","T","J","Q","K"]; //トランプの数字

for(let i = 0; i < suites.length; i++){
  for(let j = 0; j < nums.length; j++){
    console.log(suites[i] + ":" + nums[j]);
  }
}
```


今度は、

```javascript
console.log(suites[i] + ":" + nums[j]);
```

に注目して欲しい。

今回はたまたま`suites`の添字を`i`に、`nums`の添字を`j`にしてループしたのだが、両者を取り違えて`suites[j]`や`nums[i]`にアクセスしてしまう可能性はどれくらいあるだろか？

「いやいや、馬鹿にするな！それくらい覚えてるよ！！」

と言えるのは、今回のサンプルがたまたま短くて単純だからだ。

実際の現場で書くソースコードはもっと長くてもっと複雑だ。もちろん、常に短くてシンプルなコードを書くという心意気は大事だが、現実にはそうはならないことだって多々あるのも事実だ。

その前提でもう一度尋ねる。貴方は添字を取り違えたりしないだろうか？
添字を取違いが起きていることをソースレビューで指摘できるだろうか？


`Array#forEach`で書く例も再掲する。

```javascript
suites.forEach(suite => nums.forEach(num => console.log(suite + ":" + num)));
```

こちらではそもそも各要素に添字でアクセスする代わりに`suite`や`num`のような関数への引数を通じてアクセスしている。添字の取り違えなんて起きようがない。

## 理由３　副作用がないことを保証したい

極端な例で恐縮だが、

```javascript
const suites = ["H","C","D","S"]; //H:ハート C:クローバー D:ダイア S：スペード
const nums = ["A","2","3","4","5","6","7","8","9","T","J","Q","K"]; //トランプの数字

for(let i = 0; i < suites.length; i++){
  for(let j = 0; j < nums.length; j++){
    console.log(suites[i] + ":" + nums[j]);
    i = 0;
  }
}
```

こんなコードを書いたら`i = 0;`のおかげでこのループは無限ループになってしまう。

「当たり前だし、こんなことをわざとやる奴はいない」って？

ではこんなパターンはどうだろう。

```javascript
const suites = ["H","C","D","S"]; //H:ハート C:クローバー D:ダイア S：スペード
const nums = ["A","2","3","4","5","6","7","8","9","T","J","Q","K"]; //トランプの数字

for(let i = 0; i < suites.length; i++){
  for(let j = 0; j < nums.length; j++){
    console.log(suites[i] + ":" + nums[j]);
    i++;
    j++;
  }
}
```

これなら、徹夜明けか何かで疲れていて、「自分は`while`でループを書いている」と錯覚していれば起こり得そうだ。
わざとやる奴はいない？そりゃそうだ。わざとじゃないからバグなんだ。

***やる奴がいるかどうかが問題なのではなくて、起こり得てしまうことが問題***な訳で。

繰り返しになるが、このサンプルのような短いシンプルなコードで`for`を使っているのが問題なのではない。実戦で書くもっと長くて複雑なコードで問題が表面化する。

本当は書きたくも無いし読みたくも無いのだが、実戦ではこんなコードはざらに存在する。

```javascript
for(let i = 0; i < arr1.length; i++){
    /**
    * なんか100行くらいの処理
    */

    //途中で多重ループに入る。。。
    for(let j = 0; j < arr2.length; j++){
        //このループの中もとても長い。。
    }

    /**
    * さらに100行くらい。。
    */
}
```

このループが***安全***であることを確かめるために200行以上のソースを読まなければいけないのが大変な訳だ。
せめて`forEach`を使ってくれていれば***少なくともループは安全に回っていそうというのが一目で分かる***。この心理的効果が意外と大きい。


`forEach`を使うとどうなるか。

```javascript
suites.forEach(suite => nums.forEach(num => console.log(suite + ":" + num)));
```

そもそも`i`や`j`がいないので`i++;`などをやりようが無いというのもあるし、仮に何らかの事情で添字を使う場合でも、

```javascript
suites.forEach((suite, i) => nums.forEach((num, j) => {
    console.log(suite + ":" + num);
    i++;//関数の引数であるiがインクリメントされるだけなのでループは普通に回る
}));
```

このように仮に間違えがあってもループそのものは***安全***である。添字でのアクセスを避けているのでOutOfIndexにならないのも大きい。

## 理由４　結果の形や処理・変換の意図をより明確化したい

少し要求を変えて、`trump`という変数（もしくは定数）に`['H:A','H:2',...,'S:K']`という配列を格納したい場合。

`for`で書くとこう。

```javascript
let trump = [];
for(let i = 0; i < suites.length; i++){
  for(let j = 0; j < nums.length; j++){
    trump.push(suites[i] + ":" + nums[j]);
  }
}
```

`for`を避けて、今回は`forEach`ではなく`map`で書くとこうなる。

```javascript
const trump = suites.map(suite => nums.map(num => suite + ":" + num)).flat();
```

例によって、この程度の長さのシンプルなロジックであれば大した差は無い。問題は、処理が複雑化した時だ。

```javascript
let result = [];
for(let i = 0; i < arr1.length; i++){
  for(let j = 0; j < arr2.length; j++){
    /**
    * なんか色々処理。分岐もあったりとか。
    */
    result.push(somevalue);
  }
}
```

この処理を見て、最終的なresultの形を予想できるだろうか？
`arr1.length * arr2.length`の配列になる？
いやいや、途中の処理で`break;`してたら？分岐の中に`result#push`しないパスがあったら？
もっと言えば、プログラマがその複雑な処理に気を取られて、うっかり`result.push[somevalue];`を書くのを忘れていたら？
もっと極端なこと言えば、途中で`result = undefined;`なんて代入がある可能性すらある。

ここにあげた例のようなことが「無い」って証明するためには、forの中身をいちいち全部読まなければいけない。
これはしんどい。

というかそもそも、この形を見た時に、

> このプログラマはresultにarr1とarr2から生成した新たな配列を格納しようとしている

って読み取れるだろうか？読み取れるのはおそらくソースを全部読んだあとだろう。

一方で、

```javascript
const result = arr1.map(valOfArr1 => arr2.map(valOfArr2 => {
    /**
    * なんか色々処理。分岐もあったりとか。
    */
    return somevalue;
})).flat();
```

この場合は、resultの中身が長さ`arr1.length * arr2.length`の配列であることが***保証***される。

確かに、jsにおいては`return`を忘れて、全て値が`undefined`になっている可能性はある。
しかし、TypeScriptであれば型チェックに引っかかるのでreturn忘れを事前に気づくことができる。
上の`for`の例で`result.push(someValue);`を忘れた場合は型チェックでは引っかからない。

それよりも何よりも、こちらの方がプログラマが「arr1とarr2を組み合わせて新しい配列を作ろうとしている」という***意図が明確***になる。

これがかなり重要で、意図が明確かつ結果が保証されているかどうかで、

```javascript
    /**
    * なんか色々処理。分岐もあったりとか。
    */
```

を読むストレスが全然違う。これは大きい。

## （少し寄り道）可読性って何だろうね

こういう議論をしているとたまに「可読性」の定義が合わない人がいる。

例えば、

> 初心者でも（誰でも）読みやすく書くのが可読性の向上だ

という人がいるが、それは違うと思う。

その理屈が通るのであれば、日本語から漢字を撤廃して全てひらがなに統一すべきだ。その方が初心者である幼児や勉強初めたての外国人の方にとってわかりやすい。そうならないのは何故だろうか。


「読める人の多さ」と「表記としての効率性」はトレードオフだ。
幼稚園児に読ませる文章を書くときは全てひらがなで書くべきだが、それを大体の日本人の大人相手にやっていたら効率が悪い。

もっと言えば、例えばある程度オブジェクト指向を勉強した相手に、

> ここもうちょっとカプセル化しない？

って提案するのはシンプルでわかりやすいが、プログラミング覚えたての初心者には伝わらない。

> このsetterにnull突っ込んだらどうなると思う？
> インスタンス化したあと、setHoge()を呼ばずにexecuteFoo()を読んだらエラーになるよね？ちょっと工夫してみようよ。

というように噛み砕いて伝えなければいけない。「効率」を考えた時どちらの方が良いだろうか。


可読性の向上というのは、

> 想定読者のレベルをある一定だと想定して、問題なく読めてかつ効率的な表記を目指す

ことだと思う。

例えば、初心者に教えている際に

```javascript
[...Array(100).keys()].map(n => n + 1).map(n => n % 15 === 0 ? 'FizzBuzz' : n % 3 === 0 ? 'Fizz' : n % 5 === 0 ? 'Buzz' : n.toString()).forEach(s => console.log(s));
```

> こんなワンライナーを書けるようになりましょうね

って言ったら流石にバカだと思う。ちゃんと`for`で回すループを教えるべきだ。
（というか上級者相手でもここまでのワンライナーはほぼ"遊び"だろうが。。）

でも、ある程度ES6を勉強している向けに、

```javascript
const suites = ["H","C","D","S"]; //H:ハート C:クローバー D:ダイア S：スペード
const nums = ["A","2","3","4","5","6","7","8","9","T","J","Q","K"]; //トランプの数字


const trump = suites.map(suite => nums.map(num => suite + ":" + num)).flat();
```

このコードを書くのはむしろ読みやすいし、何より（今まで説明した通り）***安全***な記述だ。



前回の記事でいう、

```javascript
const N = 100;
[...Array(N).keys()].forEach(i => {
   //...なんか処理(iには0...N-1(=99)が格納される)
});
```

というハックはそのあたりのバランスが確かに微妙だとは思う。
人によっては、「可読性を損なうデメリットの方が大きい」という人もいるだろう。
もうこれ以上は宗教戦争の域に入りそうだが、それでも私は僅差で`for`を避けるメリットの方を推したい。

## それでもforを勉強すべき理由

さて、散々`for`の悪口を言っていたのだが、だからと言って`for`(や`while`)を勉強しなくても良い理由にはならない。きちんと勉強すべきだ。

理由は以下の通りだ。

### 1.様々な言語に対応できる

近頃は大体の言語で配列のmap/filter/reduceなり拡張forなりをサポートしている。あのJavaですら（失礼？）近年ではStreamAPIを導入して関数型っぽい記述をできるようになっている。

ただ、例えば私が知る限りでは例えばC言語には拡張forやmap/filter/reduceなどの組み込み関数は無いように思う。

基本的な`for`や`while`はそれぞれの言語で方言はあれどほぼ全ての言語で使える文法のはずなので、やはり勉強しておくべきである。

### 2.どうしても使わなければならない/使った方がわかりやすいロジックやアルゴリズムに直面することがある

例えば、ソートやシャッフルなど。

下記は[こちら](https://www.nxworld.net/tips/js-array-shuffle.html)から引用させていただいたフィッシャー・イエーツシャッフルの実装だが、

```javascript
const shuffle = ([...array]) => {
  for (let i = array.length - 1; i >= 0; i--) {
    const j = Math.floor(Math.random() * (i + 1));
    [array[i], array[j]] = [array[j], array[i]];
  }
  return array;
}
```

このように添字を使ってアクセスした方がはるかにわかりやすいパターンがある。

こういったアルゴリズムをわざわざ`for`を避けて書くのはあまり賢い選択では無いだろう。

### 3.漏れのある抽象化の法則

本記事の冒頭で書いたSNSで議論させていただいた相手に教えて頂いた（不勉強ながら初めて知りました。。ありがとうございます！）
かの有名なJoel on Softwareの一節で、この記事の文脈に沿って要約すると、

> Array#forEachやArray#mapを使っていて何か問題が生じた時、forやwhileの書き方を知らなかったら問題解決できないよね？

ということらしい。
それは本当にその通りだと思う。

わかりやすい例で言えば、`Array#forEach`では`break;`によるループからの離脱ができない。
どうしても`break;`が必要な場合は`for`や`while`に書き換える必要があるが、裏を返せばそれができないレベルのプログラマには`Array#forEach`を使いこなすのが難しいということだ。

## まとめ：forを"ダサい"と表現したのは軽率だったと反省している

前回の記事で、典型的な`for`文を書いて

> こんなダサいループを回したく無い

と書いた。

正直、記事冒頭で読み手を惹きつけるためのネタのつもりで書いたのだが、軽率であったと反省している。

そんなことを書いたせいかどうかはわからないが、前回紹介したようなハックに対して

> わざとトリッキーに書いて頭いいぶってカッコつけている

という印象を持たれてしまったようだ。それははっきり言って心外だ。


前述した通り、確かに

```javascript
const N = 100;
[...Array(N).keys()].forEach(i => {
   //...なんか処理(iには0...N-1(=99)が格納される)
});
```

は可読性的には微妙なラインだ。
でも、それを天秤にかけても`for`を避けたい理由がキチンとある。この記事に書いた通り、実利的な理由だ。

前回も引用した[StackOverflow](https://stackoverflow.com/questions/3746725/how-to-create-an-array-containing-1-n)の質問者さんの質問文に、

> To me it feels like there should be a way of doing this without the loop.
> 意訳： ループをしないでやる何かうまい方法があるような気がするんだ

と書いてある。
私もそれを思っていた。でも私は、こういった局面で何も工夫をせずに妥協して`for`を使っていた。

それを妥協しないでちゃんと質問した質問者さんも、それに見事なハックで答えた回答者さんも本当にかっこいいと思う。
"カッコつけている"わけではなくて。