早速ですが、for文とArrayの関数とを比較しましょう
配列に対しての操作を
- for文で行う
- Arrayの関数で行う
場合を比較してみます。
JavaScriptを例に考えていきます。
下記のようなデータを使い、「200円以上で名前が3文字以下のものを配列に格納する」ということを試してみます。
const itemNames = [
{ id: 'apple', name: 'りんご' },
{ id: 'peach', name: 'もも' },
{ id: 'grape', name: 'ぶどう' },
{ id: 'orange', name: 'オレンジ' }
];
const itemPrices = [
{ id: 'apple', price: 100 },
{ id: 'peach', price: 150 },
{ id: 'grape', price: 220 },
{ id: 'orange', price: 240 }
];
for (let i = 0; i < itemNames.length; i++) {
const itemName = itemNames[i];
if (itemName.name.length <= 3) {
let itemPrice;
for (let j = 0; j < itemPrices.length; j++) {
if (itemPrices[j].id === itemName.id) {
itemPrice = itemPrices[j];
break;
}
}
if (itemPrice && itemPrice.price >= 200) {
filteredItemNames.push(itemName.name);
}
}
}
console.log(filteredItemNames); // 出力結果: [ 'ぶどう' ]
const filteredItemNames = itemNames
.filter(itemName => itemName.name.length <=3)
.filter(itemName => {
const itemPrice = itemPrices.find(itemPrice => itemPrice.id === itemName.id);
return itemPrice && itemPrice.price >= 200;
})
.map(itemName => itemName.name);
console.log(filteredItemNames); // 出力結果: [ 'ぶどう' ]
関数を使用したほうがわかりやすいのは何故か
とても極端な例を書きましたが、(filterやmapなどを使ったことがないという人を除けば)多くの人が関数を使用した例のほうがわかりやすいと感じるのではないでしょうか。その理由は何でしょうか。
- 行数が減る
- ネストが減る
などの理由も、もちろんあるでしょうが、処理が何をしているのかわかりやすいというのが大きいです。
これは前者の例がforやifなどを用いてどうやって値を取得するのかを記述しているのに対して、後者の例は何を取得するのかを記述しているという違いがあります。
ちょっと何言っているかわからないですね。
上記を説明するために、命令型プログラミング(手続き型プログラミング)と宣言型プログラミングについて比較をしていきましょう。1
命令型プログラミングと宣言型プログラミング
まずはWikipedia先生の説明を見てみましょう。
命令型プログラミング(めいれいがたプログラミング、英: imperative programming)は、プログラムの状態(英語版)を変化させるステートメントを基本文に用いる総称的なプログラミングパラダイムである。ステートメントではコマンド(命令文)が多用される。宣言型プログラミングと対をなしてのプログラミング言語の分類用語としても扱われている。 (中略)
ステートメント上のコマンドで状態は変化され、変化した状態の参照でステートメントの動作も変化することは副作用と呼ばれる。コマンドと副作用の存在によって命令型プログラミングは、各オペレータを状態の遷移と照らし合わせて解釈することになる。このことから命令型はhow a program operates(どう処理するか)と形容される。
宣言型プログラミング(英: Declarative programming)は、数理論理学的な性質を表わしている総称的なプログラミングパラダイムである。式の計算構造を、主に表示的意味論下のロジックで表現する構文にされることが多く、式枠外の副作用を伴なう制御フローや自由変数の多用などは排除されるようになる[1]。計算構造は演繹的に組み立てられることが多い。命令型プログラミングと対をなしてのプログラミング言語の分類用語としても扱われている。
宣言型言語は、what the program must accomplish(何をなすべきか)方針で、副作用を排除した式や純粋関数の実装に努める[2]。これは命令型言語の、how to accomplish it(どうなすべきか)方針で、副作用を前提にした操作的意味論下のアルゴリズム実装とよく対比される[3]。
1回読んだだけで理解するのは難しいですね...。
今回重要になるのは、
命令型はhow a program operates(どう処理するか)と形容される
宣言型言語は、what the program must accomplish(何をなすべきか)方針
というところです。
これを踏まえて先程の例に立ち返ってみましょう。
命令形の例
先程のfor文とif文で書かれた例を振り返ってみます。
for (let i = 0; i < itemNames.length; i++) {
const itemName = itemNames[i];
if (itemName.name.length <= 3) {
let itemPrice;
for (let j = 0; j < itemPrices.length; j++) {
if (itemPrices[j].id === itemName.id) {
itemPrice = itemPrices[j];
break;
}
}
if (itemPrice && itemPrice.price >= 200) {
filteredItemNames.push(itemName.name);
}
}
}
console.log(filteredItemNames); // 出力結果: [ 'ぶどう' ]
処理の内容を日本語で書くと
- itemNamesの配列の長さの回数だけ繰り返し処理を行う
- itemNameの要素を取得する
- 名前の長さが3以下の場合分岐を行う
- itemPricesの配列の長さの回数だけ繰り返し処理を行う
- itemPriceのidとitemNameのidが一致する場合分岐を行う
- itemPriceを変数に格納する
- itemPricesの繰り返し処理を終了する
- itemPriceのidとitemNameのidが一致する場合分岐を行う
- itemPriceが存在して価格が200以上の場合分岐を行う
- 名前をfilteredItemnamesに格納する
- itemPricesの配列の長さの回数だけ繰り返し処理を行う
このようになります。
「200円以上で名前が3文字以下のものを配列に格納する」という仕様を知らない場合、上記の処理を見て処理の意図を推測するのは(できるにしても)大変さを伴うでしょう。
命令型(手続き型)では、どうやって値を取得するのかを記述するため、
- 要素を一時変数に入れる
- for文で繰り返し処理をする
- if文で分岐処理をする
というコードを書いています。
宣言型の例
先程の関数で書かれた例を見てみます。
const filteredItemNames = itemNames
.filter(itemName => itemName.name.length <=3)
.filter(itemName => {
const itemPrice = itemPrices.find(itemPrice => itemPrice.id === itemName.id);
return itemPrice && itemPrice.price >= 200;
})
.map(itemName => itemName.name);
console.log(filteredItemNames); // 出力結果: [ 'ぶどう' ]
1つ目のfilterでは「名前の長さが3以下でしぼる」、2つ目のfilterでは「価格が200以上でしぼる」、次のmapでは「名前のみに変換する」というように
何を取得するのかという記述に終始しています。
どうやるのか(how)ではなく、何をするのか(what)を記述されていれば、処理を見るだけで処理の意図を推測するのが比較的容易です。
まとめ(forのループじゃなくて、mapやfilterなどの関数を使う理由)
一言で言えば可読性が高いからです。
可読性が高い理由は、
- 行数が減る
- ネストが減る
などもありますが、本質的には
どうやるのか(how)ではなく、何をするのか(what) が記述されることになり、理解しやすいコードとなるからです。
補足
今回は「for文じゃなくてmapとかfilterを使うほうが良い」理由についてフォーカスすることを目的としていたため、
宣言型プログラミングの一面に触れました。
今回説明していない利点も色々ありますので気になったら調べてみてください。
参考
- 命令型プログラミング
- 宣言型プログラミング
- なぜfor文は禁止なのか?関数型記述のススメ
- 宣言的? Declarative?どういうこと?
- 現代のオブジェクト指向の class の割れ窓化と宣言的プログラミング
-
命令型プログラミングと手続き型プログラミングは概念的に似ているため、一緒のもののように扱われることが多いみたいです ↩