前にもやりましたが、今回は違うアプローチで任意の数の関数を合成する関数composeを作ってみます。
二つの関数を合成する
まんまですが:
const compose2 = (f2, f1) => x => f2( f1( x ) );
二個の関数 f2, f1 と x を引数にとって、xにf1を適用してその結果にf2を適用する、ってことです。
三つの関数を合成する
これもまんまです。
const compose3 = (f3, f2, f1) => x => f3( f2( f1( x ) ) );
3個の関数 f3, f2, f1 と x を引数にとって、xにf1を適用してその結果にf2を適用してその結果にf3を適用する、ってことです。
n個の関数を合成する
類推すればこんなイメージになると思います。ちゃんとしたコードじゃないですが。
const composeN = (fn, ... ,f2,f1) => x => fn( ... f2( f1( x ) ) ... );
n個の関数 fn, ... , f2, f1 と x を引数にとって、xにf1を適用してその結果にf2を適用して...という風にfnまで順に関数を適用していく、ってことです。
これで何個の関数を合成する関数でも書けるようになりました。
compose5 でも compose100 でも自由自在です。
え?そんな関数、書きたくない? 長ったらしいわりに使い回しが効かず全然便利じゃない? そうですよね....
#任意の個数の関数を合成するには?
簡単です。残余引数とreduceRightを使えばよい。
残余引数rest parameters
引数を ( ...fs ) と書くと、関数定義内で引数を fs という名前の配列として扱えます。"..."は何かの省略のてんてんではなく、れっきとした構文要素です。
const compose = ( ...fs ) => x =>(配列fsとxを使ったなにかの処理);
みたいな感じで書けます。
reduceRight
残余引数を使って引数が配列で扱えるようになったので、配列のメソッドがそのまま使えます。
reduceRightはreduceの右から版です。配列の最後の方から適用されていきます。あとはreduceと同じ。
この場合は、初期値 x に配列の要素(=関数)を適用しその結果にまた次の要素を適用していくので、アキュムレータをacc、配列 fs の要素を f として、コールバック関数を(acc, f) => f(acc)
としとけば良い。
fs.reduceRight( (acc, f) => f(acc), x )
まとめると...
const compose = ( ...fs ) => x => fs.reduceRight( ( acc, f ) => f( acc ) , x );
できたー。
ちなみに:他のやりかただと...
//再帰:
const composeR = (...fs) => x =>
fs.length === 0 ? x
: composeR( ...fs.slice( 0, -1 ) )( fs[ fs.length - 1 ]( x ) )
;
//for loop:
const composeFor = (...fs) => x => {
let acc = x;
for(let i = fs.length-1; i >= 0; i--) acc = fs[i](acc);
return acc;
};