本稿ではPHPで関数合成する方法を紹介する。
関数合成とは
関数合成とは2つの関数を組み合わせて1つの関数を作ることだ。この手法は、シンプルで小さく凝集性と再利用性の高い関数を組み合わせることで、保守性を保ちつつ多様な要求に応える処理を実現するために使われたりする。
関数合成してみる
ここに2つの関数がある。
$addOne = function (int $value): int {
return $value + 1;
};
$timesTwo = function (int $value): int {
return $value * 2;
};
これを合成してひとつの関数を作ってみる。
$addOneTimesTwo = function (int $value) use ($addOne, $timesTwo): int {
$value = $addOne($value);
return $timesTwo($value);
};
echo $addOneTimesTwo(4); //=> 10
これで関数合成できたわけだが、関数合成するためにその都度クロージャを書くのは手間になりそうだ。
関数合成する関数を作る
もっといい合成のしかたがある。それは、関数合成する関数を作ることだ。例えば、$compose
という関数合成関数を作って、引数に関数を渡したら、さっきの$addOneTimesTwo
と同じロジックを持つ関数が作られるようにしたい:
$addOneTimesTwo = $compose($addOne, $timesTwo);
これは命令型の関数合成と違って、処理の順と記述の順が一致していて読みやすい。
関数合成関数$compose
を実装してみよう。
$compose = function (callable $f, callable $g): callable {
return function (...$arguments) use ($f, $g) {
return $g($f(...$arguments));
};
};
$compose
関数は2つの関数を受け取って、その2つを呼び出せる新しい関数(接着剤的な役割をする関数)を作って返す。
クライアントコードの読みやすさのために$compose
は$f
→$g
の順で受け取るようにしつつ、合成される関数の呼び出し順序は後(先($引数))
ではならないので、合成の式は$g
→$f
の順で記述する必要がある。これを逆にしてしまうとバグるので注意。
function (...$arguments)
の...
は可変長引数。つまり、あらゆる値をいくつでも受け取れるようにしている。$g($f(...$arguments))
の...
はsplat演算子と呼ばれるもので、配列の$arguments
を関数に渡す際に一個一個の引数に展開する効果がある。
それでは関数合成して動かしてみよう:
$addOneTimesTwo = $compose($addOne, $timesTwo);
echo $addOneTimesTwo(4); //=> 10
一度にたくさんの関数を合成できる関数を作る
上で作った関数合成関数は2つの関数を受け取り、関数合成してくれる実装になっていたが、一度に3つ以上の関数を合成したいこともあるだろう。
ということで、たくさんの関数を合成できる関数を作ってみよう。
// さきほど作った関数合成関数
$compose = function (callable $f, callable $g): callable {
return function (...$arguments) use ($f, $g) {
return $g($f(...$arguments));
};
};
// たくさんの関数を合成できる関数
$composeAll = function (callable $function, callable ...$functions) use ($compose): callable {
return array_reduce($functions, $compose, $function);
};
上の$composeAll
は$compose
を再利用する形になっているが、次のようにべた書きしても同じ挙動になる。
$composeAll = function (callable $function, callable ...$functions): callable {
return array_reduce(
$functions,
function (callable $f, callable $g): callable {
return function (...$arguments) use ($f, $g) {
return $g($f(...$arguments));
};
},
$function
);
};
動かしてみよう:
$addOne = function (int $value): int {
return $value + 1;
};
$timesTwo = function (int $value): int {
return $value * 2;
};
$minusTwo = function (int $value): int {
return $value - 2;
};
$addOneTimesTwoMinusTwo = $composeAll($addOne, $timesTwo, $minusTwo);
echo $addOneTimesTwoMinusTwo(1); //=> 2
((1 + 1) * 2) - 2 が計算されて 2 が出力される。