PHP

PHP: Generatorの関数合成

本稿ではGeneratorを関数合成してひとつのGeneratorを作る方法を紹介する。

関数合成関数を実装する方法は、「PHP: 関数合成する関数を作る方法 - Qiita」で紹介した。ここで紹介した関数合成関数を使えば、Generatorも合成することができる。


どんな合成をしたいか?

3つのジェネレーターがある。これを合成して、ひとつのジェネレーターを作りたい。

function generator(string $name): callable

{
return function (iterable $items) use ($name): iterable {
foreach ($items as $item) {
echo "generator('$name') yields $item\n";
yield $item;
}
};
}

// 3つのジェネレーター
$a = generator('a');
$b = generator('b');
$c = generator('c');

手続き型で書くとこんな感じ:

$composedGenerator = function (iterable $items): iterable {

$a = generator('a');
$b = generator('b');
$c = generator('c');
yield from $c($b($a($items)));
};

foreach ($composedGenerator([1, 2, 3]) as $item) {
echo "item = $item\n";
}

これを実行すると、次のような出力が出る:

generator('a') yields 1

generator('b') yields 1
generator('c') yields 1
item = 1
generator('a') yields 2
generator('b') yields 2
generator('c') yields 2
item = 2
generator('a') yields 3
generator('b') yields 3
generator('c') yields 3
item = 3


関数合成関数で合成する

関数合成関数はこれを使う:

$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
);
};

合成のしかた:

$composedGenerator = $composeAll(generator('x'), generator('y'), generator('z'));

実行してみる:

foreach ($composedGenerator([1, 2, 3]) as $item) {

echo "item = $item\n";
}

実行結果:

generator('x') yields 1

generator('y') yields 1
generator('z') yields 1
item = 1
generator('x') yields 2
generator('y') yields 2
generator('z') yields 2
item = 2
generator('x') yields 3
generator('y') yields 3
generator('z') yields 3
item = 3

合成できた。


別解

foreachする方法もある。

function compose(callable ...$functions): callable

{
return function (iterable $items) use ($functions): iterable {
foreach ($functions as $function) {
$items = $function($items);
}
return $items;
};
}

$composedGenerator = compose($a, $b, $c);

foreach ($composedGenerator([1, 2, 3]) as $item) {
echo "item = $item\n";
}