関数合成を徹底解説 – JavaScript

JavaScriptにおいて、複数個の関数を一つの関数に合成する関数合成の方法を解説します。

ネストした関数

まずは、ネスト関数を考えてみましょう。

+1する関数と、2倍にする関数の組み合わせを考えます。

function increment(x) {
  return x + 1;
}
function dobble(x) {
  return x * 2;
}
console.log(increment(dobble(2))); //5
console.log(dobble(increment(2))); //6

+1してから2倍にした場合と、2倍にしてから+1した場合とで計算結果が異なっていることに着目してください。

ネストした関数ではなく、一つの関数にまとめる場合、計算する順番によって答えが違うのでその分だけ関数を作らなければいけません。

実際に、計算の順番が違う関数を作成してみます。

命令型の関数合成

function increment(x) {
  return x + 1;
}
function dobble(x) {
  return x * 2;
}
function dobbleInc(x) {
  let ans = x;
  ans = dobble(ans);
  ans = increment(ans);
  return ans;
}
function incDobble(x) {
  let ans = x;
  ans = increment(ans);
  ans = dobble(ans);
  return ans;
}
console.log(dobbleInc(2)); //5
console.log(incDobble(2)); //6

このように、「2倍してから1を足す関数」と「1を足してから2倍する関数」の2つを作成しなければなりません。

しかし、ただ計算の順番を入れ替えた関数を作るのはDRYの原則に則っているとは言えません。

そこで、関数自体を一つにまとめる宣言型の関数合成を使います。

宣言型の関数合成

先程のコードを宣言型の関数合成で表すと次のようになります。

function increment(x) {
  return x + 1;
}
function dobble(x) {
  return x * 2;
}
const calcCompose = (f1, f2) => x => f2(f1(x));
  
console.log(calcCompose(dobble, increment)(2)); //5
console.log(calcCompose(increment, dobble)(2)); //6

JavaScritpでは、関数の返り値に関数を指定できます。calcComposeの返り値にアロー関数 x => f2(f1(x)); を指定し、最初にf1を計算しその結果を引数としてf2に渡し計算しています。

これにより関数合成calcComposeを使えば、計算の順序を入れ替えたい場合に引数の順番を入れ替えるだけで済みます。

コードも非常にシンプルになり、わかりやすくなりました。

上記の例では、2個の関数を合成しているだけですが、n個の関数合成も容易に実現できます。

複数個(n個)の宣言型関数合成

// 複数個(n個)の関数合成
const calcManyCompose = (...fns) => x => fns.reduce( (composed, f) => f(composed), x);

// +1、2倍、2倍、+1する処理 => (3+1) × 2 × 2  + 1 = 17
console.log(calcManyCompose(increment, dobble, dobble, increment)(3)); //17

n個の関数を処理する場合、スプレッド構文...Array.reduceを使うと簡単に実現できます。

Array.reduceは第一引数にコールバック関数で、第二引数にコールバック関数の初期値が入ります。

つまり、配列内の最初の関数は引数xで呼び出され、それ以降は前の関数の返り値が次の関数の引数(composed)として渡されます。そのようにして、順番に関数が呼び出され、最後の返り値が最終的な返り値として返されます。

宣言型の合成関数のメリットは、非常にシンプルに書くことができ、何をしているかパッと見で理解できるところです。

React.jsやNode.jsを使っている方は、ほぼ必ず利用する機会があると思うのでしっかり理解することをお勧めします。

参考文献

関数合成で書く!メンテナンスしやすいJavaScriptコードの書き方
JavaScriptに関数型プログラミングでアプローチするメリットは、小さくて理解しやすい個々の関数を用い、複雑な関数を構築できることです。しかし、もっともエレガントなソリューションを見出すには、ときには逆の方向から問題を見ることが必要になります。 本記事ではJavaScriptの関数合成について分析し、それによってな...

コメント