PHP/入門/関数とは
関数の定義と呼出し[編集]
PHPでは、ひとまとまりの処理に名前をつけ、あとから何度も呼出せるようにする仕組みがあります。 これを関数(かんすう;【英】Function)といいます。 関数を作ることを、「関数定義」関数を使うことを「関数呼出し」といいます。
PHPには、すでに有用な関数が多数用意されており、これらを「組込み関数」と呼びます。
プログラマーは、組込み関数・文・式を組合わせて新しい関数を定義することができます。 新しく定義された関数を「ユーザー定義関数」と呼びます。
- ユーザー定義関数の例(1)
<?php function f() { echo "abc", PHP_EOL; echo "123", PHP_EOL; } f(); f(); f(); ?>
- 実行結果
abc 123 abc 123 abc 123
- ユーザー定義関数
f()
を定義し、3回呼出しています。 - 関数定義の書式:
function 関数名(引数リスト) { // 処理 }
上のプログラムは、次のようにもかけます。
- ユーザー定義関数の例(2)
<?php f(); f(); f(); function f() { echo "abc", PHP_EOL; echo "123", PHP_EOL; } ?>
- 関数の定義の前に、関数を呼出しを行っていますが、PHPの場合は「関数の前方参照」は処理系が自動的に解決するので、前方参照のための関数宣言は必要ありません(逆にできません)。
- 前方参照は処理系が解決してくれますが、人間が読むときには前方参照が多いと(定義を見て)引数/戻値やざっくりした処理を理解する前に関数呼出しが来てしまうと読解しづらいので、なるべく定義→呼出しの順になるようにすると良いでしょう。
- 正しく動くこと、、だけでなく読みやすさも大切です。
値を返す関数[編集]
関数は、呼出し元に値を返すことができます。
値を返すには return
文を使い、return
は単一の式を取ります。
もし、return
に値を与えないと、その関数の戻値型は void 型となります。
例えば、下記のコードは、単純に関数fが1を返す関数です。
<?php function f() { return 1; } echo f(); ?>
- 実行結果
1
- と表示されます。
- 参考
<?php echo pi(); ?>
- 実行結果
3.1415926535898
pi()
は、円周率の返す関数です。
関数の引数と戻値[編集]
PHPの関数は、0個以上の引数を渡し1つの戻値を返すことができます。 可変長引数や引数のディフォルト値、またキーワード引数もサポートしています。
- 例
<?php declare(strict_types=1); function add($i, $j) { return $i + $j; } echo 'add(20, 99) --> ', add(20, 99), PHP_EOL; function sum(...$args) { $sum = 0; foreach ($args as $arg) { $sum += $arg; } return $sum; } echo 'sum(1,2,3,4,5,6,7,8,9,10) --> ', sum(1,2,3,4,5,6,7,8,9,10), PHP_EOL; $ary = array(); for ($i = 1; $i <= 100; $i++) { $ary[$i] = $i; } echo 'sum(...$ary) --> ', sum(...$ary), PHP_EOL; function acc($i, $j = 1) { return $i + $j; } echo 'acc(20) --> ', acc(20), PHP_EOL; echo 'acc(20, 99) --> ', acc(20, 99), PHP_EOL; function wishes($age, $name = 'You') { $postfix = 'th'; switch ($age % 10) { case 1: $postfix = 'st'; break; case 2: $postfix = 'nd'; break; case 3: $postfix = 'rd'; break; } echo "Happy $age$postfix birthday to $name!", PHP_EOL; } wishes(13); wishes(name:"Tom", age:21); wishes(age:12, name:"Alice"); wishes(age:100, name:"My granny"); ?>
- 実行結果
add(20, 99) --> 119 sum(1,2,3,4,5,6,7,8,9,10) --> 55 sum(...$ary) --> 5050 acc(20) --> 21 acc(20, 99) --> 119 Happy 13rd birthday to You! Happy 21st birthday to Tom! Happy 12nd birthday to Alice! Happy 100th birthday to My granny!
- スプレッド構文を使えば可変長引数の関数も定義できます。
- 引数が省略されたときのディフォルト値を与えることもできます。
- キーワード引数もサポートしています。
型宣言[編集]
型宣言は、関数の引数や返り値、そして PHP 7.4.0 以降ではクラスのプロパティに追加することができます。 これらは、呼出し時に指定した型の値であることを保証し、 一致しない場合は TypeError がスローされます。 strict_types を 1 にセットすると、型宣言漏れをエラーにできます。
- 例
<?php declare(strict_types=1); $y = 7; $fn1 = fn(int $x) : int => $x + $y; $fn2 = function (int $x) use ($y) : int { return $x + $y; }; echo '$fn1(3) --> ', $fn1(3), PHP_EOL; echo '$fn2(3) --> ', $fn2(3), PHP_EOL; $y = 1; echo '$fn1(3) --> ', $fn1(3), PHP_EOL; echo '$fn2(3) --> ', $fn2(3), PHP_EOL; ?>
- 実行結果
10119
- 型宣言のある関数定義の書式:
function 関数名(引数型1 引数名1, 引数型2 引数名2, … 引数型n 引数名n) : 戻値型 { // 処理 }
有効な型[編集]
プリミティブ型のほか、クラスやインターフェースの名前は、型名として認識されます。
PHPの関数の引数や戻値の型 型 説明 バージョン classまたはinterfacd の名前 値は与えられたクラスまたはインターフェースのインスタンスでなければなりません。 self 値は型宣言が使用されているクラスと同じクラスのインスタンスでなければなりません。クラス内でのみ使用可能。 parent 型宣言が使われているクラスの親のインスタンスでなければなりません。クラス内でのみ使用可能。 array 値は配列でなければなりません。 callable 値は有効な callable である必要があります。クラスのプロパティの型宣言としては使用できません。 bool 値はブール値でなければなりません。 float 値は浮動小数点数でなければなりません。 int 値は整数値でなければなりません。 string 値は文字列でなければなりません。 iterable 値は、配列か Traversable のインスタンスでなければなりません。 PHP 7.1.0 object 値はオブジェクトでなければなりません。 PHP 7.2.0 mixed 値は任意の値であることができます。 PHP 8.0.0
変数のスコープ[編集]
PHPでは、名前空間のトップレベルで代入された変数をグローバル変数と呼び、関数内で代入された変数をローカル変数と呼びます。 PHPに変数宣言はないので、最初の代入が宣言に準じた扱いになります。 関数内のローカル変数は(static 宣言されなければ)動的で、関数を抜けると揮発します。 関数の中から、グローバル変数を参照するには、3つの方法があります。
- 例
<?php declare(strict_types=1); $i = 10; echo __FUNCTION__, ": \$i= {$i}", PHP_EOL; function g() { global $i; echo __FUNCTION__, ": \$i= {$i}", PHP_EOL; $i = 99; } g(); echo __FUNCTION__, ": \$i= {$i}", PHP_EOL; function a() { $i = &$GLOBALS['i']; echo __FUNCTION__, ": \$i= {$i}", PHP_EOL; $i = 42; } a(); echo __FUNCTION__, ": \$i= {$i}", PHP_EOL; function r(&$i) { echo __FUNCTION__, ": \$i= {$i}", PHP_EOL; $i = 4423; } r($i); echo __FUNCTION__, ": \$i= {$i}", PHP_EOL; ?>
- 実行結果
: $i= 10 g: $i= 10 : $i= 99 a: $i= 99 : $i= 42 r: $i= 42 : $i= 4423
- __FUNCTION__ は、実行中の関数を文字列返すマジック定数です。
- トップレベルでは、 ”” を返します。
g()
- キーワード global で宣言されたグローバル変数は代入を含む参照ができます。
a()
$GLOBALS
はグローバル変数を要素とするハッシュで、代入を含む参照ができます。r()
- グローバル変数へのリファレンスを関数の引数として渡すと、リファレンス経由で代入を含む参照ができます。
3つ方法がありますが、どれも使わないと関数からのグローバル変数の参照は PHP Warning: Undefined variable $eee
になり失敗します。
static変数[編集]
関数の内側で作られた変数は関数を抜けると揮発します。
関数から抜けても、次回の呼出しで値を保持してほしいときは、ローカル変数を static 宣言します。
- 例
<?php declare(strict_types=1); function sacc() : void { static $i = 0; echo __FUNCTION__, ': $i = ', $i, PHP_EOL; $i++; } sacc(); sacc(); sacc(); sacc(); ?>
- 実行結果
sacc: $i = 0 sacc: $i = 1 sacc: $i = 2 sacc: $i = 3
関数内で関数を定義しても関数スコープにはならない |
関数の中で別の関数を定義することは可能ですが意味はありません。
|
無名関数[編集]
関数の中で関数を定義してもクロージャーにはなりませんが、無名関数でクロージャーを実現できます。 PHPには、無名関数を定義する方法に、アロー関数と関数式の2つがあります。
- 例
<?php $y = 7; $fn1 = fn($x) => $x + $y; $fn2 = function ($x) use ($y) { return $x + $y; }; echo '$fn1(3) --> ', $fn1(3), PHP_EOL; echo '$fn2(3) --> ', $fn2(3), PHP_EOL; $y = 1; echo '$fn1(3) --> ', $fn1(3), PHP_EOL; echo '$fn2(3) --> ', $fn2(3), PHP_EOL; ?>
- 実行結果
$fn1(3) --> 10 $fn2(3) --> 10 $fn1(3) --> 10 $fn2(3) --> 10
fn($x) => $x + $y;
の=>
が矢印っぽいのでアロー関数と呼ばれます。- この記号は
{ 'a' => 1 }
(連想配列)でも使われます。
- この記号は
function ($x) use ($y) { return $x + $y;};
は、関数式です。- 関数定義とよく似ていますが名前がありません。
- use でキャプチャーする環境を指定します。
- キャプチャー
- 上の実行結果をよく見ると9行目
$y = 1;
のあとも答えが変わっていません。 - このようにアロー関数や関数式(まとめて無名関数)は、定義されたときの環境をキャプチャーします。
- このようキャプチャーを行う性質から、無名関数は、しばしまクロージャーと呼ばれます。
- 無名関数の中で __FUNCTION__ を参照すると、
{closure}
となります。
- 無名関数の中で __FUNCTION__ を参照すると、
再帰的呼出し[編集]
関数は、自分の定義の中に自分を含めることができます。
- 例
<?php declare(strict_types=1); function ipow(int $x, int $y) : int { if ($y == 0) return 1; if ($y == 1) return $x; return $x * ipow($x, $y - 1); } echo 'ipow(2, 3) --> ', ipow(2, 3), PHP_EOL; echo 'ipow(10, 9) --> ', ipow(10, 9), PHP_EOL; function comma3(int $n) { if ($n < 0) { return "-" . comma3(-$n); } $num = intdiv($n, 1000); $rem = $n % 1000; if ($num) { return comma3($num) . sprintf(",%03d", $rem); } return sprintf("%d", $rem); } echo 'comma3(ipow(10, 9)) --> ', comma3(ipow(10, 9)), PHP_EOL; echo 'comma3(ipow(2, 32)) --> ', comma3(ipow(2, 32)), PHP_EOL;
- 実行結果
ipow(2, 3) --> 8 ipow(10, 9) --> 1000000000 comma3(ipow(10, 9)) --> 1,000,000,000 comma3(ipow(2, 32)) --> 4,294,967,296
- 整数の累乗を求める関数
ipow()
と、整数を3桁ごとに ',' で区切ってた文字列を返す関数comma3()
を定義しました。 - 2つの関数とも定義が再帰的なので、再帰的呼出しで実装しています。
- 再帰的呼出しは、終了条件を間違えると「無限再帰」に陥ってしまい、資源(この場合はスタック)を喰いつくすまで帰ってきません。
- アルゴリズムが再帰的な場合、再帰的呼出しを行う関数として実装するとコードは短くなりますが、PHPの処理系はtailrecのような最適化は行わないので、関数呼出しのオーバーヘッドが再帰のたびに課金され、資源にも優しくないので、再帰で実装したあと回帰テストを書き、ループで実装しテストを通ることを確認するのがベターでしょう。
ジェネレター[編集]
ジェネレター関数は、値を返す代わりに、yield
で必要なだけの値を生成することを除けば、通常の関数と同じように見えます。
yield
を含む関数はすべてジェネレター関数です。
ジェネレター関数が呼出されると、繰返し実行可能なオブジェクト(iterable
型のオブジェクト)が返されます。
そのオブジェクトに対して反復処理を行うと (たとえば foreach ループで)、 PHP は値が必要になるたびにオブジェクトの反復処理用メソッドをコールし、 ジェネレターが値を返した時点でその状態を保存して、次の値が必要になったときに再開できるようにします。
生成する値がなくなったら、ジェネレターは単に終了し、呼出し元のコードは配列の値がなくなったときと同じように続行できます。
- 例
<?php declare(strict_types=1); function gen_square() : iterable { for ($i = 0; $i < 10; $i++) { yield $i * $i; } } foreach (gen_square() as $value) { echo "$value\n"; } var_export(array(...gen_square()));
- 実行結果
0 1 4 9 16 25 36 49 64 81 array ( 0 => 0, 1 => 1, 2 => 4, 3 => 9, 4 => 16, 5 => 25, 6 => 36, 7 => 49, 8 => 64, 9 => 81, )
ジェネレターは配列に似ていますが、配列は 要素数 × 要素1つあたりのサイズ のメモリーフットプリントを消費します。それに対しジェネレターは、yield
で返す値は都度計算できるのでメモリー消費は配列に比べ小さくなります。
コードキャラリー[編集]
まとまった規模で、実用的な目的に叶うコードを読まないと機能の存在理由などの言語設計に思い至りにくいので、コードの断片から少し踏み込んだプログラムを掲載します。
array_*関数の拡張[編集]
PHPの組込み関数には、array_reduce(),array_map() のように array_ はじまる配列関数群があります。 これらは便利なのですが、配列とハッシュ兼用でJavaScriptやRubyなどから来ると面食らうことがあります。 また、配列の添字がcallback関数にわたらない仕様も移植のときに問題になります。 そこで、上記の問題をクリアした配列専用の関数群を作ってみました。 また、sum() は、カハンの加算アルゴリズムを使い誤差を最小にするようにしています。
- 実装
<?php // phpinfo(); declare(strict_types=1); function reduce(array $ary, callable $code, mixed $result = null): mixed { $i = 0; foreach ($ary as $el) { if ($i == 0 && !isset($result)) { $result = $el; } else { $result = $code($result, $el, $i); } $i++; } return $result; } echo 'reduce([1, 2, 3], fn($s, $x) => $s + $x) --> ', reduce([1, 2, 3], fn($s, $x) => $s + $x), PHP_EOL; echo 'reduce([1, 2, 3], fn($s, $x) => $s + $x, 10) --> ', reduce([1, 2, 3], fn($s, $x) => $s + $x, 10), PHP_EOL; echo 'reduce(["A", "B", "C"], fn($s, $x) => $s . $x) --> ', reduce(["A", "B", "C"], fn($s, $x) => $s . $x), PHP_EOL; echo 'reduce(["A", "B", "C"], fn($s, $x) => [ ...$s, "<$x>"], []) --> ', var_export(reduce(["A", "B", "C"], fn($s, $x) => [...$s , "<$x>"], []), true),PHP_EOL; echo PHP_EOL; function map(array $ary, callable $code): array { $result = []; $i = 0; foreach ($ary as $el) { $result[] = $code($el, $i++); } return $result; } $a1 = [2, 3, 5, 7, 11]; echo '$a1 = [2, 3, 5, 7, 11];', PHP_EOL; echo 'map($a1, fn($x) => $x * $x) --> ', var_export(map($a1, fn($x) => $x * $x), true), PHP_EOL; echo PHP_EOL; function each(array $ary, callable $code): void { $i = 0; foreach ($ary as $el) { $code($el, $i++); } } echo 'each($a1, fn($x, $i) => printf("%d[%d] ", $x, $i)); --> '; each($a1, fn($x, $i) => printf("%d[%d] ", $x, $i)); echo PHP_EOL; echo PHP_EOL; function filter(array $ary, callable $code): array { $result = []; $i = 0; foreach ($ary as $el) { if ($code($el, $i++)) { $result[] = $el; } } return $result; } echo 'filter($a1, fn($x) => $x % 2) --> ', var_export(filter($a1, fn($x) => $x % 2), true), PHP_EOL; echo 'filter($a1, fn($x) => $x < 6) --> ', var_export(filter($a1, fn($x) => $x < 6), true), PHP_EOL; echo PHP_EOL; function sum(array $ary): int|float|null { $sum = null; $i = 0; foreach ($ary as $delta) { if ($i == 0) { $sum = $delta; $c = $delta - $delta; } else { $y = $delta - $c; $t = $sum + $y; $c = ( $t - $sum ) - $y; $sum = $t; } $i++; } return $sum; } $nums = array_fill(0, 10, 0.1); echo '$nums = array_fill(0, 10, 0.1);', PHP_EOL; echo 'reduce($nums, fn($x, $y) => $x + $y) --> ', sprintf("%.53f", reduce($nums, fn($x, $y) => $x + $y)), PHP_EOL; echo 'array_sum($nums) --> ', sprintf("%.53f", array_sum($nums)), PHP_EOL; echo 'sum($nums) --> ', sprintf("%.53f", sum($nums)), PHP_EOL; echo 'sum(range(1, 100)) --> ', sprintf("%d", sum(range(1, 100))), PHP_EOL; echo 'sum([]) --> ',var_export(sum([]), true), PHP_EOL; echo PHP_EOL; function every(array $ary, callable $code): bool { $i = 0; foreach ($ary as $el) { if (!$code($el, $i++)) { return false; } } return true; } echo 'every($a1, fn($x) => $x != 1) --> ', var_export(every($a1, fn($x) => $x != 1), true), PHP_EOL; echo 'every($a1, fn($x) => $x == 2) --> ', var_export(every($a1, fn($x) => $x == 2), true), PHP_EOL; echo PHP_EOL; function some(array $ary, callable $code): bool { $i = 0; foreach ($ary as $el) { if ($code($el, $i++)) { return true; } } return false; } echo 'some($a1, fn($x) => $x != 1) --> ', var_export(some($a1, fn($x) => $x != 1), true), PHP_EOL; echo 'some($a1, fn($x) => $x == 2) --> ', var_export(some($a1, fn($x) => $x == 2), true), PHP_EOL; echo PHP_EOL;
- 実行結果
reduce([1, 2, 3], fn($s, $x) => $s + $x) --> 6 reduce([1, 2, 3], fn($s, $x) => $s + $x, 10) --> 16 reduce(["A", "B", "C"], fn($s, $x) => $s . $x) --> ABC reduce(["A", "B", "C"], fn($s, $x) => [ ...$s, "<$x>"], []) --> array ( 0 => '<A>', 1 => '<B>', 2 => '<C>', ) $a1 = [2, 3, 5, 7, 11]; map($a1, fn($x) => $x * $x) --> array ( 0 => 4, 1 => 9, 2 => 25, 3 => 49, 4 => 121, ) each($a1, fn($x, $i) => printf("%d[%d] ", $x, $i)); --> 2[0] 3[1] 5[2] 7[3] 11[4] filter($a1, fn($x) => $x % 2) --> array ( 0 => 3, 1 => 5, 2 => 7, 3 => 11, ) filter($a1, fn($x) => $x < 6) --> array ( 0 => 2, 1 => 3, 2 => 5, ) $nums = array_fill(0, 10, 0.1); reduce($nums, fn($x, $y) => $x + $y) --> 0.99999999999999988897769753748434595763683319091796875 array_sum($nums) --> 0.99999999999999988897769753748434595763683319091796875 sum($nums) --> 1.00000000000000000000000000000000000000000000000000000 sum(range(1, 100)) --> 5050 sum([]) --> NULL every($a1, fn($x) => $x != 1) --> true every($a1, fn($x) => $x == 2) --> false some($a1, fn($x) => $x != 1) --> true some($a1, fn($x) => $x == 2) --> true