php8 で追加され使えるようになった match 式。
従来の switch に似たようなものではあるけれど、一番の違いは switch が == による弱い比較であることに対して、match は型と値の一致チェックである === によるものであること。これにより弱い比較により内在する想定外のバグのために使いづらいというか、使用不可としていた switch の代用として使えるものということ。

match 式

match 式の書式は連想配列に似てる。
以下、php.net からの引用でまとめたもの。

<?php
    $food = 'cake';

    $return_value = match ( $food ) {
        'apple' => 'This food is an apple',
        'bar', 'snack' => 'This food is a bar',// 複数の条件をカンマ区切りで含める事が可能。 これは論理ORであり、複数の分岐の右辺を同じにする場合の短縮記法
        'cake' => 'This food is a cake',
        default => 'No food',// 上の分岐にマッチしなかったあらゆる場合にマッチするdefault
    }; // 必ずセミコロン ; で終わらなければならない

    var_dump( $return_value );
    // 上の出力 -> string(19) "This food is a cake"
?>
PHP
CopyExpand

match 式と switch 文の似て異なるいくつかの違い

match 式は 緩い比較により使えなかった switch 文の代替として考えられたものであろうから、その switch 文においての問題を払拭するように刷新されているのだと思われる。

  • match 式の比較は、 switch 文が行う弱い比較ではなく、 厳密に値を比較( === ) する
  • match 式は値を返す( match 式の結果は、必ずしも使う必要はない )
  • match 式の分岐は、switch 文のように後の分岐に抜けたりはしない
  • match 式は、全ての場合を網羅していなければいけない

そのほか、厳密な一致チェックを行わずに match 式を使う書き方もあるし、下のように右辺の分岐に条件式を直接書くこともできる。詳しくは -> php.net manual match

<?php
    $text = 'Bienvenue chez nous';

    $result = match ( true ) {// 制約式に true を指定することで、 厳密な一致チェックをさせない
        str_contains( $text, 'Welcome' ) || str_contains( $text, 'Hello' ) => 'en',
        str_contains( $text, 'Bienvenue' ) || str_contains( $text, 'Bonjour' ) => 'fr',
        // ...
    };

    var_dump($result);
    // 上の出力 -> string(2) "fr"
?>
PHP
CopyExpand

それと、match 式の各候補の返却式には三項演算子も書けるし呼び出す関数を指定することもできる。そのあたりをいろいろとやっていて、関数を指定できるならクロージャ(無名関数)も書けるのでは?と、やってみたが、それにはちょっと工夫が必要だった。ここにありー>「stackoverflow – Use closures in match arms [duplicate]」

<?php
    $randnum = mt_rand( 0, 9 );

    match ( $randnum ) {
        0 => echo_num( 'zero' ),
        1 => echo_num( 'one' ),
        2 => echo_num( 'two' ),
        4,5 =>  $randnum & 1 ? echo_num( 'odd' ) : echo_num( 'even' ), // 三項演算子
        6,7,8 => ( function() use( $randnum ) { // 即時実行関数を使って closure 呼び出し
                $num = $randnum * 10;
                echo_num( ( string ) $num );
            })(),
        default => call_user_func( function() use( $randnum ) { // これも closure 呼び出し call_user_func()を使用
                $num = $randnum * 100;
                echo_num( ( string ) $num );
            }),
    };

    function echo_num( string $num = 'none' ): void {
        echo '<p> echo num : ' . $num . '</p>';
    }
?>
PHP
CopyExpand

なお、ちょっと注意が必要なことといえば、返却式で echo を使って文字列を出力することはできない。でも、この場合は print が使える。

<?php
    for ( $i = 0; $i < 5; ++$i ) {
        $rand = mt_rand( 0, 100 ) % 9;

        match ( $rand ) {
            0 => print '<p>余り 0</p>',
            1,2,3=> print '<p>余り 1-3</p>',
            4 => print '<p>余り 4</p>',
            // 5 => echo '<p>余り 5</p>// ',✗ echo は使えない
            default => print '<p>余り >=5</p>',
        };
    }
?>
PHP
CopyExpand

match、if、array lookup-table、のベンチテスト 文字列編

と、いうことでこの match 式。これはもしかしたら if で書くよりも速くなる気がして期待大である。ならば早速ベンチをやってみるしかない。で、もう一つ、使えるところが限られるけれど、if よりも断然高速な配列のルックアップテーブルを使った方法。これも交えてまずは単純な文字列を返す処理で試してみた。

if 文においてはその条件項が多くなればなるほど当然遅くなる。その場合、階層にできるなら階層化して、通過する条件項の数を極力減らすことが高速化の手段となる。それゆえ、条件が少ない場合と多い場合とで違いが出るであろうということが想像にやさしい。なので、条件が5個の場合と10個の場合で調べてみた。ループのカウンターから作った数値を指定した個数で割った余りの数で分岐させるというだけの単純なもの。

<?php
    echo '<p> match 文字列 5pcs 10,000回</p>';

    $result = array();

    for ( $j = 1; $j < 11; ++$j ) {
        $ansnum = '';
        $gettime = array();
        $ans = 0;

        $gettime[0] = microtime( true );

        for ( $i = 1; $i < 10001; ++$i ) {
            $tar = ( $i * $j ) % 5;

            $ret_val = match ( $tar ) {
                0 => 'zero',
                1 => 'one', 
                2 => 'two',
                3 => 'three',
                default => 'four',
            };
            $ansnum = $ret_val;
        }

        $gettime[1] = microtime( true );
        $ans = $gettime[1] - $gettime[0];
        $result[] = $ans;
        echo '<p>result : ' . sprintf( '%0.5f', $ans ) . '秒</p>';
    }
    echo '<p>ave : ' . sprintf( '%0.5f', array_sum( $result ) / count ( $result ) ) . ' max : ' . sprintf( '%0.5f', max( $result ) ) . ' min : ' . sprintf( '%0.5f', min( $result ) ) . '</p>';
    $result = array();

    echo '<p> if 文字列 5pcs 10,000回</p>';

    for ( $j = 1; $j < 11; ++$j ) {
        $ansnum = '';
        $gettime = array();
        $ans = 0;

        $gettime[0] = microtime( true );

        for ( $i = 1; $i < 10001; ++$i ) {
            $tar = ( $i * $j ) % 5;

            if ( 0 === $tar ) {
                $ret_val = 'zero'; 
            } elseif ( 1 === $tar ) {
                $ret_val = 'two'; 
            } elseif ( 2 === $tar ) {
                $ret_val = 'two';
            } elseif ( 3 === $tar ) {
                $ret_val = 'three'; 
            } else {
                $ret_val = 'four'; 
            }
            $ansnum = $ret_val;
        }

        $gettime[1] = microtime( true );
        $ans = $gettime[1] - $gettime[0];
        $result[] = $ans;
        echo '<p>result : ' . sprintf( '%0.5f', $ans ) . '秒</p>';
    }
    echo '<p>ave : ' . sprintf( '%0.5f', array_sum( $result ) / count ( $result ) ) . ' max : ' . sprintf( '%0.5f', max( $result ) ) . ' min : ' . sprintf( '%0.5f', min( $result ) ) . '</p>';
    $result = array();

    echo '<p> Array-lookup-table 文字列 5pcs 10,000回</p>';

    $funkname = array( 'zero', 'one', 'two', 'three', 'four', );

    for ( $j = 1; $j < 11; ++$j ) {
        $ansnum = '';
        $gettime = array();
        $ans = 0;

        $gettime[0] = microtime( true );

        for ( $i = 1; $i < 10001; ++$i ) {
            $tar = ( $i * $j ) % 5;

            $ansnum = $funkname[ $tar ];
        }

        $gettime[1] = microtime( true );
        $ans = $gettime[1] - $gettime[0];
        $result[] = $ans;
        echo '<p>result : ' . sprintf( '%0.5f', $ans ) . '秒</p>';
    }
    echo '<p>ave : ' . sprintf( '%0.5f', array_sum( $result ) / count ( $result ) ) . ' max : ' . sprintf( '%0.5f', max( $result ) ) . ' min : ' . sprintf( '%0.5f', min( $result ) ) . '</p>';
    $result = array();

    echo '<br><br><br><p> match 文字列 10pcs 10,000回</p>';


    for ( $j = 1; $j < 11; ++$j ) {
        $ansnum = '';
        $gettime = array();
        $ans = 0;

        $gettime[0] = microtime( true );

        for ( $i = 1; $i < 10001; ++$i ) {
            $tar = ( $i * $j ) % 10;

            $ret_val = match ( $tar ) {
                0 => 'zero',
                1 => 'one', 
                2 => 'two',
                3 => 'three',
                4,5 => 'five',
                6,7 => 'six',
                8 => 'eight',
                default => 'nine',
            };
            $ansnum = $ret_val;
        }

        $gettime[1] = microtime( true );
        $ans = $gettime[1] - $gettime[0];
        $result[] = $ans;
        echo '<p>result : ' . sprintf( '%0.5f', $ans ) . '秒</p>';
    }
    echo '<p>ave : ' . sprintf( '%0.5f', array_sum( $result ) / count ( $result ) ) . ' max : ' . sprintf( '%0.5f', max( $result ) ) . ' min : ' . sprintf( '%0.5f', min( $result ) ) . '</p>';
    $result = array();


    echo '<p> if 文字列 10pcs 10,000回</p>';

    for ( $j = 1; $j < 11; ++$j ) {
        $ansnum = '';
        $gettime = array();
        $ans = 0;

        $gettime[0] = microtime( true );

        for ( $i = 1; $i < 10001; ++$i ) {
            $tar = ( $i * $j ) % 10;

            if ( 0 === $tar ) {
                $ret_val = 'zero'; 
            } elseif ( 1 === $tar ) {
                $ret_val = 'two'; 
            } elseif ( 2 === $tar ) {
                $ret_val = 'two';
            } elseif ( 3 === $tar ) {
                $ret_val = 'three';
            } elseif ( 4 === $tar or 5 === $tar ) {
                $ret_val = 'five'; 
            } elseif ( 6 === $tar or 7 === $tar ) {
                $ret_val = 'six'; 
            } elseif ( 8 === $tar ) {
                $ret_val = 'eight';   
            } else {
                $ret_val = 'nine'; 
            }
            $ansnum = $ret_val;
        }

        $gettime[1] = microtime( true );
        $ans = $gettime[1] - $gettime[0];
        $result[] = $ans;
        echo '<p>result : ' . sprintf( '%0.5f', $ans ) . '秒</p>';
    }
    echo '<p>ave : ' . sprintf( '%0.5f', array_sum( $result ) / count ( $result ) ) . ' max : ' . sprintf( '%0.5f', max( $result ) ) . ' min : ' . sprintf( '%0.5f', min( $result ) ) . '</p>';
    $result = array();

    echo '<p> Array-lookup-table 文字列 10pcs 10,000回</p>';

    $funkname = array( 'zero', 'one', 'two', 'three', 'four', 'five', 'six', 'seven', 'eight', 'nine' );

    for ( $j = 1; $j < 11; ++$j ) {
        $ansnum = '';
        $gettime = array();
        $ans = 0;

        $gettime[0] = microtime( true );

        for ( $i = 1; $i < 10001; ++$i ) {
            $tar = ( $i * $j ) % 10;

            $ansnum = $funkname[ $tar ];
        }

        $gettime[1] = microtime( true );
        $ans = $gettime[1] - $gettime[0];
        $result[] = $ans;
        echo '<p>result : ' . sprintf( '%0.5f', $ans ) . '秒</p>';
    }
    echo '<p>ave : ' . sprintf( '%0.5f', array_sum( $result ) / count ( $result ) ) . ' max : ' . sprintf( '%0.5f', max( $result ) ) . ' min : ' . sprintf( '%0.5f', min( $result ) ) . '</p>';
?>
PHP
CopyExpand

ベンチテスト 文字列編結果

それぞれ10,000回のループで得られる結果を、各10回づつとり、その平均値、最大、最小の値が下の表となる。テスト環境はといえば。
Windows10 Pro 64bit
Aapche2.4
php8.2

> Firefox Developer Edition 109.0b7 (64bit)

5pcs match if lookup table ave .00053 .00061 .00032 max .00067 .00070 .00034 min .00045 .00040 .00031
10pcs match if lookup table ave .00045 .00090 .00030 max .00056 .000128 .00031 min .00043 .00039 .00030

> Chrome canary 111.0.5509.0(64bit)

5pcs match if lookup table ave .00047 .00078 .00031 max .00060 .00135 .00032 min .00042 .00039 .00031
10pcs match if lookup table ave .00044 .00093 .00033 max .00046 .00157 .00041 min .00042 .00061 .00030

5pcs とある表は条件項が5個、10pcs は10個のテスト。予想通り if においては条件分岐が増えれば遅くなる。が match はほぼ影響がないよう。array lookup table においても同様でこちらも数が増えようが全く関係ないようである。

match、if、array lookup-table、のベンチテスト 関数編

そして今度は、各条件により呼び出す関数を指定する場合。
これまで、こういう場合は、ほぼ if 文を使っていたというか、使うしかなかったわけで。これも配列による lookup table で可変関数を使って関数を呼び出すことは出来るけれど、たしか以前にベンチしたときは、 if 文よりもかなり遅くなってしまったという記憶があり、使うことがなかったということであるけれど。

ちなみに可変関数に関してはここにあり -> php.net 可変関数
php は、変数名の後に括弧が付いている場合、その値が何であろうと同名の関数を探し実行を試みるということ。簡単にいえば、変数に入れてある文字列にて関数を指定し呼び出せるということ。配列の lookup-table と組み合わせて使えば速そうなんだけれどね・・・。

<?php
    // 呼び出す関数たちの下準備
    function add_two ( $target ) {
        return $target + 2;
    }

    function add_three ( $target ) {
        return $target + 3;
    }

    function add_four ( $target ) {
        return $target + 4;
    }

    function add_five ( $target ) {
        return $target + 5;
    }
    // ここまで

    echo '<br><br><br><p> match func 10,000回</p>';

    for ( $j = 1; $j < 11; ++$j ) {
        $ansnum = array();
        $gettime = array();
        $ans = 0;

        $gettime[0] = microtime( true );

        for ( $i = 1; $i < 10001; ++$i ) {
            $tar = ( $i * $j ) % 5;

            $ret_val = match ( $tar ) {
                0 => add_two( $i ),
                1 => add_three( $i ), 
                2, 3 => add_four( $i ),
                default => add_five( $i ),
            };
            $ansnum[] = $ret_val;
        }

        $gettime[1] = microtime( true );
        $ans = $gettime[1] - $gettime[0];
        $result[] = $ans;
        echo '<p>result : ' . sprintf( '%0.5f', $ans ) . '秒</p>';
    }
    echo '<p>ave : ' . sprintf( '%0.5f', array_sum( $result ) / count ( $result ) ) . ' max : ' . sprintf( '%0.5f', max( $result ) ) . ' min : ' . sprintf( '%0.5f', min( $result ) ) . '</p>';
    $result = array();

    echo '<p> if func 10,000回</p>';

    for ( $j = 1; $j < 11; ++$j ) {
        $ansnum = array();
        $gettime = array();
        $ans = 0;

        $gettime[0] = microtime( true );

        for ( $i = 1; $i < 10001; ++$i ) {
            $tar = ( $i * $j ) % 5;

            if ( 0 === $tar ) {
                $ret_val = add_two( $i ); 
            } elseif ( 1 === $tar ) {
                $ret_val = add_three( $i ); 
            } elseif ( 2 === $tar or 3 === $tar ) {
                $ret_val = add_four( $i ); 
            } else {
                $ret_val = add_five( $i ); 
            }
            $ansnum[] = $ret_val;
        }

        $gettime[1] = microtime( true );
        $ans = $gettime[1] - $gettime[0];
        $result[] = $ans;
        echo '<p>result : ' . sprintf( '%0.5f', $ans ) . '秒</p>';
    }
    echo '<p>ave : ' . sprintf( '%0.5f', array_sum( $result ) / count ( $result ) ) . ' max : ' . sprintf( '%0.5f', max( $result ) ) . ' min : ' . sprintf( '%0.5f', min( $result ) ) . '</p>';
    $result = array();

    echo '<p> 可変関数 func 10,000回</p>';

    $funkname = array( 'add_two', 'add_three', 'add_four', 'add_four', 'add_five' );

    for ( $j = 1; $j < 11; ++$j ) {
        $ansnum = array();
        $gettime = array();
        $ans = 0;

        $gettime[0] = microtime( true );

        for ( $i = 1; $i < 10001; ++$i ) {
            $tar = ( $i * $j ) % 5;

            $call_tar = $funkname[ $tar ];
            $ret_val = $call_tar( $i ); 

            $ansnum[] = $ret_val;
        }

        $gettime[1] = microtime( true );
        $ans = $gettime[1] - $gettime[0];
        $result[] = $ans;
        echo '<p>result : ' . sprintf( '%0.5f', $ans ) . '秒</p>';
    }
    echo '<p>ave : ' . sprintf( '%0.5f', array_sum( $result ) / count ( $result ) ) . ' max : ' . sprintf( '%0.5f', max( $result ) ) . ' min : ' . sprintf( '%0.5f', min( $result ) ) . '</p>';
?>
PHP
CopyExpand

ベンチテスト 関数編結果

こちらもそれぞれ10,000回のループで得られる結果を、各10回づつとり、その平均値、最大、最小の値が下の表となる。

> Firefox Developer Edition 109.0b7 (64bit)

match if lookup table 可変関数 ave .00090 .00101 .00108 max .00118 .00144 .00143 min .00083 .00072 .00092

> Chrome canary 111.0.5509.0(64bit)

match if lookup table 可変関数 ave .00087 .00106 .00114 max .00093 .00158 .00166 min .00083 .00074 .00095

関数呼び出しに関しては match を使うべきのよう。やはりというか可変関数を使うと遅くなってしまう。
各条件が単純なリテラルで得られ( 配列のキーとなり得るものという意味 )、かつ不確定なものが無く、関数呼び出しではない場合のみ、配列での lookup-table を使い、あとは迷うことなく match を使うべきという結果である。

Leave a Reply!

JavaScript is necessary to send a comment.
You can edit and delete your comment if you input a edit key.
Edit key is necessary for attesting you when you edit and delete it.
The tag of HTML cannot be used in comment.
When you comment for the first time, it is displayed after the approval of the administrator.
Because I cannot speak English so much, it takes time to answer.
Required fields are marked *.

※Please enter more than 5 characters only alphabets.
※Edit or delete are possible for 2000 days after approval.

*

♠Simplistic Comment User Editable v4.0

♠When visitors leave comments on the site this site collect the data shown in the comments form, and also the visitor’s IP address and browser user agent string to help spam detection.
♠This site does not use cookie when visitors leave comments and commenter edit comment.
♠This site uses Akismet to reduce spam. Learn how your comment data is processed.

Comments feed

Trackback URL : https://strix.main.jp/wp-trackback.php?p=173742

Sanbanse Funabashi
2011.01.01 sunrise

Top

スクロールさせるか画像をクリックすると元に戻ります。