Posted at

array_mapと部分適用

More than 3 years have passed since last update.

PHP - 部分適用ってなんだに続くポエムですが、どうしてもPHPでarray_mapとかarray_reduceとかを駆使して宣言的に書きたいんだ! って人向けです。

前回のポエムでは「foreachがあれば、別に部分適用とか要らないし」と結論を出しました。この記事は蛇足もいいところで、まったくおすすめしません。読まなくていいです。












これが前回のポエムの最終的な結論でした。

$a = [];

foreach (range(1, $n) as $m) {
$a[] = area_trapezoid($height, 3, 5)
}

$result = $a;

しかし一時変数を作って破壊的操作してくのは嫌だ! って感じかたもありそうです。

嫌なのは仕方ないので、foreachはやめよう。そうです、array_map()です。


array_map

array_map — 指定した配列の要素にコールバック関数を適用する

array array_map ( callable $callback , array $array1 [, array $... ] )

array_map() は、array1 の各要素に callback 関数を適用した後、 その全ての要素を含む配列を返します。 callback 関数が受け付けるパラメータの数は、 array_map() に渡される配列の数に一致している必要があります。

PHP: array_map - Manual より抜萃


なるほど。callbackに渡すcallableとは何かについては PHP: コールバック / Callable - Manual を読んでください。


適用って何だっけ

「関数を適用」とは耳馴染みがないかもしれませんが、例を示します。

/**

* 値を2倍にする
*/

function Nx2($n)
{
return $n * 2;
}

$data = [1, 2, 3, 4, 5];
$result = array_map("Nx2", $data);
//=> [2, 4, 6, 8, 10]

配列に「値を2倍にする」函数を適用することで、全ての値が2倍になりました。

では次に「値をm倍にする」函数を適用してやりたい。

/**

* 値をM倍にする
*/

function NxM($n, $m)
{
return $n * $m;
}

これをarray_mapを使って、全ての値を3倍に適用するにはどうすればいいか。

NxMは引数を二つ要求するので、配列を二つ渡してやればいいですね。

$data1 = [1, 2, 3, 4, 5];

$data2 = [3, 3, 3, 3, 3];

$result = array_map("NxM", $data1, $data2);
//=> [3, 6, 9, 12, 15]

$dataの要素が$nに、$data2の要素が$mに入ります。

これはちょっとめんどくさい… いや、array_fill(0, count($data), 3)とかでdataと同じ要素数の配列を作ることはできるけど、$dataが100万要素あったとしたら、100万個の3だけが並んだ配列を生成するのは、いくらなんでも無駄すぎるだろ。

array_mapからn個の引数を渡すにはn個の配列」が必要なので、わざわざ2個の配列を作りました。

しかし、「array_mapから1個の引数だけ渡されれば良い」ようにすれば、配列は1個で済みますね。前回のポエムで長々と部分適用の種類を紹介したのは何のためだったか。

$data = [1, 2, 3, 4, 5];

$Nx3 = function($a) { return NxM($a, 3); };

$result = array_map($Nx3, $data);
//=> [3, 6, 9, 12, 15]

やった、配列がひとつで済んだ! だらだらとしたポエムで、ようやく「foreach」以外の部分適用を活用できましたね。

これで引数がいくつあっても怖くない。







話を戻す

$a = [];

foreach (range(1, $n) as $m) {
$a[] = area_trapezoid($height, 3, 5)
}

$result = $a;

$t3_5 = function ($height) {

return area_trapezoid($height, 3, 5);
};
$result = array_map($t3_5, range(1, $n));

短くなった…? わかりやすい…?

いや、どっちも意図は明確ではあるんだけど、foreacharray_mapになっても別に嬉しくはないだろ、ってのが前回の記事を強引にまとめた理由です。

しかし、「短くしたい」「コードを明確にしたい」 が大差ない以上、もっと宣言的に書きたくなるのが人情です。

そのためにPartialCallable.phpを用意したよ。

$t3_5 = new PartialCallable("area_trapezoid", [1 => 3, 2 => 5], 0);

$result = array_map($t3_5, range(1, 10));

new PartialCallableの第一引数はcallable、第二引数は部分適用する引数を配列で指定、第三引数はarray_mapから渡された引数をどの位置に部分適用するかの指定です。

PHPの配列は0オリジンなのでわかりにくいですが、0が第一引数、1が第二引数、2が第三引数… とずれます。

よかったよかった。これで人類に平和が訪れます。


結論

foreachでいいじゃん。


完全な蛇足

日本語で「部分適用」で検索すると、たいていカレー料理の話が出てきて読者を混乱させます。 PHPにカリー化の出番は一切ありません

どうしても、どうしてもカリー化とは何か気になる人は「カリー化ってなぁに? をRubyから。」と、「Rubyでのカリー化、をまじめに。」を読んで、 二度とPHPの話題でカリー化を持ち込まない方がいいです

得るものは特にないから読まなくていいし、何度でも言ふけどPHPに カリー化の出番は一切ない から忘れてください。