はじめに
ナイトウ(@engineer_naito)と申します。
JavaScriptの勉強をしているのですが、本当に難しいことばかりです。
今回は最近ハマった Array.prototype.reduce()
について記事にしようと思います。
ぼく「Array.prototype.reduce()
って何?」
ぼく「何をreduceしてるの?」
TL;DR
Array.prototype.reduce()
は、配列の各要素に対して縮小関数を実行し、その結果を単一の値にまとめるためのJavaScriptのメソッドです。
reduceは「減らす」ではなく、関数型プログラミングにおける 「畳み込み」 を意味しています。
Array.prototype.reduce()
reduce() は Array インターフェイスのメソッドで、配列のそれぞれの要素に対して、ユーザーが提供した「縮小」コールバック関数を呼び出します。
コールバックの初回実行時には「直前の計算の返値」は存在しません。 初期値が与えらえた場合は、代わりに使用されることがあります。 そうでない場合は、配列の要素 0 が初期値として使用され、次の要素(0 の位置ではなく 1 の位置)から反復処理が開始されます。
例
const array1 = [1, 2, 3, 4];
// 0 + 1 + 2 + 3 + 4
const initialValue = 0;
const sumWithInitial = array1.reduce(
(accumulator, currentValue) => accumulator + currentValue,
initialValue,
);
console.log(sumWithInitial);
// Expected output: 10
配列 array1
の要素を初期値 initialValue
にどんどん加えていき、その結果を返しています。
構文
reduce(callbackFn)
reduce(callbackFn, initialValue)
callbackFn
callbackFn
には引数を4つまで引数を取ることができます。
順番 | 名前 | 役割 |
---|---|---|
第一引数 | accumurator |
前回の callbackFn の結果 |
第二引数 | currentValue |
現在の要素 |
第三引数 | currentIndex |
現在の位置 |
第三引数 | array |
reduce が呼び出された配列 |
initialValue
callbackFn
が最初に呼び出された際、accumulator
が初期化される値です。
返り値
配列全体にわたって「縮小」コールバック関数を実行した結果の値です。
実際の使われ方
Vue公式コード例集より
https://vuejs.org/examples/#grid
<script setup>
// ...
// props.columns は ['name', 'power']
const sortOrders = ref(
props.columns.reduce((o, key) => ((o[key] = 1), o), {})
);
// ...
</script>
このコードはVueのAPIを用いているので構造をVanillaJSに抜き出します。
// columns は ['name', 'power']
const sortOrders = columns.reduce((o, key) => ((o[key] = 1), o), {});
JS素人のぼくはこれ見たときかなりビビりました。
reduce()
だけではなく、ぼくをビビらせた理由がもう一つあります。
それはカンマ演算子です。
上記コードの理解にはカンマ演算子についての理解も必要です。
https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Operators/Comma_operator
sortOrders
は {'name': 1, 'power': 1}
というオブジェクトになります。
ぼくは全くわかりませんでした。
なぜ難しいのか
このメソッドについてたくさん記事があります。
難しいと感じている人が多いのでしょう。
Array.prototype.reduce()
が難しいと感じてしまうのはなぜでしょうか?
以下のような理由が考えられます。
- コールバック引数を取る関数(高階関数)が難しい
- 万能である(と評価されている)
- reduceという言葉が難しい(直感的ではない)
コールバック引数を取る関数が取らないものより難しいのは当たり前だと思うので置いておきます。
2つ目の理由「万能」とはどういうことなのか。
先ほど挙げた記事でも言及されていますが、 map
, some
, forEach
, filter
などを reduce
を用いて書き換えることができるためです。
これが「万能」たる所以なのですが、これが逆に器用貧乏のような感じに繋がっている気がします。
ぼくも「forEach
と何が違うんだ?」という疑問が頭に浮かんだまま reduce
の勉強を続けています。
最後の理由がぼくの理解を一番遅らせた理由です。
reduce、減らすという意味ですよね。
ぼくは小学生のときにこの言葉を初めて知りました。
テレビで見たのか社会の教科書に載っていたのか、、、
(「3R運動」(リデュース、リユース、リサイクル))
単語の意味を知っていたのですが、reduce()
が何を減らしているのかがよくわからなかったんですよね。
プログラミングにおける"reduce"
実はプログラミングにおける"reduce"は、 「畳み込み」 を表します。
プログラミングにおける「畳み込み」は"fold", "reduce"であって、数学での「畳み込み(convolution)」とは特に関係がないようです。
https://ja.wikipedia.org/wiki/%E7%95%B3%E3%81%BF%E8%BE%BC%E3%81%BF
プログラミングにおける畳み込みについては「重畳関数」をご覧ください。
「畳み込み」 は関数型プログラミングにおける中心的な概念だそうです。
関数型プログラミングにおける"reduce"の意味で、Array.prototype.reduce()
は使われているのですね。
知識がないのでわかりませんが、きっと「畳み込み」も"reduce"の基本的な意味である「減らす」に由来しているのでしょう。
ちなみに
海外エンジニアの方も Array.prototype.reduce()
を難しいと思っている方もいらっしゃるようです。
まとめ
Array.prototype.reduce()
は、配列の各要素に対して縮小関数を実行し、その結果を単一の値にまとめるためのJavaScriptのメソッドでした。
reduceは関数型プログラミングにおける 「畳み込み」 を意味しているようです。
"reduce"の意味「減らす」にこだわると、かえって混乱してしまいます。
(公式ドキュメントの日本語版では「縮小」という言葉を使っていますが、、、)
最後に
思えば"run"もいろんな意味があります。
「走る」、「経営する」、「出馬する」、、、
プログラミングにおける"run"は「実行」の意味で使われることが多いですよね。
"reduce"にもいろんな意味があります。
先入観にとらわれずに学習を続けたいです。
最後まで読んでいただきありがとうございました!