アロー関数は短い無名関数です:fn($x) => $x * 2。=>のあとの本体は1つの式で、その値が返され、式が使う周りのコードの変数は自動的に値でキャプチャされます。アロー関数はPHP 7.4から使え、おもにコールバックとして使われます。
<?php
$double = fn($x) => $x * 2;
echo $double(21), "\n";
$prices = [10, 25, 40];
print_r(array_map(fn($p) => $p * 1.2, $prices));
$rate = 0.9; // used inside without "use"
print_r(array_map(fn($p) => $p * $rate, $prices));
構文
fn、引数リスト、=>、1つの式の順に書きます。引数の型、デフォルト値、戻り値の型は通常の関数と同じように使えます。
<?php
$add = fn(int $a, int $b = 1): int => $a + $b;
$greet = fn(string $name): string => "Hello, $name";
$isEven = fn(int $n): bool => $n % 2 === 0;
$now = fn() => 'no parameters';
echo $add(2, 3), ' ', $add(2), "\n";
echo $greet('Ada'), "\n";
var_dump($isEven(4), $isEven(7));
echo $now(), "\n";
// Call it immediately
echo (fn($x) => $x ** 2)(9), "\n";
returnキーワードも波括弧もありません。長い式も、読みやすさのために複数行に分けられます。
外側の変数は値でキャプチャされる
アロー関数は、作られたスコープのすべての変数を参照できます。その時点の値をコピーするので、中での変更は中にとどまり、関数を作ったあとの外での変更は見えません。
<?php
$count = 0;
$increment = fn() => ++$count;
echo $increment(), "\n"; // works on its own copy
echo $increment(), "\n"; // a fresh copy each call
echo $count, "\n"; // the outer variable is unchanged
$discount = 10;
$apply = fn($price) => $price - $discount;
$discount = 50; // too late: 10 was already captured
echo $apply(100), "\n";
これが、変数を参照で共有できるfunction () use (&$count)との大きな違いです。アロー関数は外側の変数に再代入できません。ただし、キャプチャしたオブジェクトは変更できます(コピーは同じオブジェクトへのハンドルだからです)。また参照として宣言した引数は、どの関数とも同じように動きます:fn(array &$list) => $list[] = 'x'。
array_map、array_filter、usortのコールバック
ほとんどの配列関数はコールバックを受け取り、アロー関数がいちばん記述を減らしてくれるのはそこです。同じ絞り込みを両方の書き方で比べてみましょう。
<?php
$users = [
['name' => 'Ada', 'age' => 36, 'active' => true],
['name' => 'Linus', 'age' => 28, 'active' => false],
['name' => 'Grace', 'age' => 45, 'active' => true],
];
// Long form
$active1 = array_filter($users, function ($u) {
return $u['active'];
});
// Arrow function
$active2 = array_filter($users, fn($u) => $u['active']);
var_dump($active1 === $active2);
$names = array_map(fn($u) => $u['name'], $active2);
echo implode(', ', $names), "\n";
usort($users, fn($a, $b) => $a['age'] <=> $b['age']);
echo implode(', ', array_column($users, 'name')), "\n";
$total = array_reduce($users, fn($sum, $u) => $sum + $u['age'], 0);
echo "Total age: $total\n";
<=>は-1、0、1を返し、これはusort()が求めるものそのものなので、フィールドでの並べ替えが1行に収まります。降順にするには$aと$bを入れ替えます。
アロー関数を返すアロー関数
キャプチャされる変数には外側のアロー関数の引数も含まれるので、ネストしてほかの値から関数を作れます。
<?php
$multiplyBy = fn($factor) => fn($x) => $x * $factor;
$triple = $multiplyBy(3);
$tenTimes = $multiplyBy(10);
echo $triple(5), ' ', $tenTimes(5), "\n";
$between = fn($min, $max) => fn($n) => $n >= $min && $n <= $max;
$teen = $between(13, 19);
print_r(array_filter([8, 13, 16, 21, 19], $teen));
array_filter()は元のキーを保つので、結果が添字1から始まっている点に注意してください。
fnとfunction:完全なクロージャが必要なとき
本体に複数の式が必要なときや、外側の変数を変更しなければならないとき、アロー関数は使えません。そのときは無名関数を使います。
| fn($x) => ... | function ($x) use ($y) { ... } |
|---|
| 本体 | 1つの式 | 任意の文 |
| 戻り値 | 自動 | 明示的なreturn |
| 外側の変数 | 使ったものすべてを自動で | useに並べたものだけ |
| 参照でキャプチャ | できない | できる、use (&$y) |
| 使えるようになった版 | PHP 7.4 | PHP 5.3 |
<?php
$orders = [120, 45, 300, 80];
// Needs a statement and an outer total: a closure with use (&...)
$total = 0;
$big = [];
array_walk($orders, function ($amount) use (&$total, &$big) {
$total += $amount;
if ($amount > 100) {
$big[] = $amount;
}
});
echo "Total $total, big orders: ", implode(', ', $big), "\n";
// The same result split into expressions: arrow functions fit again
$total2 = array_sum($orders);
$big2 = array_values(array_filter($orders, fn($a) => $a > 100));
echo "Total $total2, big orders: ", implode(', ', $big2), "\n";
アロー関数が窮屈に感じるときは、たいていその処理が2つの処理であることを意味しています。上のように絞り込みと合計に分ければ、各コールバックを1つの式に保てます。