Mobile Factory Tech Blog

技術好きな方へ!モバイルファクトリーのエンジニアたちが楽しい技術話をお届けします!

Perl5.42の変更点

こんにちは、エンジニアの id:mp0liiu です。

非常に遅くなってしまいましたが、昨年の7/4にPerlの最新安定バージョンである5.42がリリースされたので新機能や変更点についてまとめます。

source::encoding プラグマが追加され、デフォルトで有効に

スコープ内のソースコードに期待する文字コードの指定をするプラグマ source::encoding が追加されました。 指定できるのは asciiutf8 のみです。 use source::encoding 'ascii' するとスコープ内のソースコードに非ASCII文字が存在している場合、コンパイルエラーが発生するようになります。 use source::encoding 'utf8'use utf8 と同等です。 v5.41 以降の feature bundle1 ではデフォルトで use source::encoding 'ascii' が有効になります。

従来では次のように use utf8 していないのに非ASCII文字を扱うようなコードは、エラーになることなく意図していない挙動をしてしまうことがありました。

say length "あいうえお"; # 本当は5文字だが, use utf8 していないので 15 が出力される

use source::encoding 'ascii' することで、そのようなコードはコンパイルエラーになるので事前に気づくことができるようになります。

use v5.42; # use source::encoding 'ascii' も有効になる
say length "あいうえお"; # コンパイルエラー: Use of non-ASCII character 0xE3 illegal when 'use source::encoding "ascii"'

これからは日本語など非ASCII文字を使うコードはきちんと use utf8 してから書くようにしましょう。

なお、source::encoding 'ascii' はコメントやPODでも非ASCII文字があるとコンパイルエラーになるので注意が必要です。
__DATA__, __END__ セクション以降に書く分には問題ありません。

any, all 演算子の追加

List::Util の any, all と同じ挙動をします。

演算子として実装されているためコードブロックのスタックフレームが作られずより高速に実行できるとのことでしたが、ベンチマークをいろいろとってみたところ大きいパフォーマンスの差はないものの、コードブロック内の処理やリストの要素数によってどちらの方がパフォーマンスがよいかが変わってしまいました。 なのでこだわる場合は自分で該当部分のパフォーマンスを計測することをおすすめします。

参考までに List::Util と関数と演算子とでそれぞれベンチマークをとったので参考にしてみてください。

keywordのほうがパフォーマンスが良い場合(any)
Benchmark: running keyword_any, list_util_any for at least 1 CPU seconds...
keyword_any:  2 wallclock secs ( 1.13 usr +  0.00 sys =  1.13 CPU) @ 495.58/s (n=560)
list_util_any:  1 wallclock secs ( 1.11 usr +  0.00 sys =  1.11 CPU) @ 355.86/s (n=395)
               Rate list_util_any   keyword_any
list_util_any 356/s            --          -28%
keyword_any   496/s           39%            --

keywordのほうがパフォーマンスが良い場合(all)
Benchmark: running keyword_all, list_util_all for at least 1 CPU seconds...
keyword_all:  1 wallclock secs ( 1.11 usr +  0.00 sys =  1.11 CPU) @ 503.60/s (n=559)
list_util_all:  1 wallclock secs ( 1.04 usr +  0.00 sys =  1.04 CPU) @ 358.65/s (n=373)
               Rate list_util_all   keyword_all
list_util_all 359/s            --          -29%
keyword_all   504/s           40%            --

List::Utilのほうがパフォーマンスが良い場合(any)
Benchmark: running keyword_any, list_util_any for at least 1 CPU seconds...
keyword_any:  2 wallclock secs ( 1.06 usr +  0.00 sys =  1.06 CPU) @ 16.04/s (n=17)
list_util_any:  1 wallclock secs ( 1.06 usr +  0.00 sys =  1.06 CPU) @ 17.92/s (n=19)
                Rate   keyword_any list_util_any
keyword_any   16.0/s            --          -11%
list_util_any 17.9/s           12%            --

List::Utilのほうがパフォーマンスが良い場合(all)
Benchmark: running keyword_all, list_util_all for at least 1 CPU seconds...
keyword_all:  1 wallclock secs ( 1.05 usr +  0.00 sys =  1.05 CPU) @ 16.19/s (n=17)
list_util_all:  1 wallclock secs ( 1.06 usr +  0.00 sys =  1.06 CPU) @ 17.92/s (n=19)
                Rate   keyword_all list_util_all
keyword_all   16.2/s            --          -10%
list_util_all 17.9/s           11%            --

ベンチマークに使用したコードはこちら

use v5.42;
use Benchmark qw( timethese cmpthese );
use List::Util ();
use utf8;
binmode STDOUT, ':encoding(UTF-8)';

my @ary = (1 .. 1000000);

say "keywordのほうがパフォーマンスが良い場合(any)";
cmpthese(
    timethese(-1, +{
        keyword_any => sub {
            use experimental qw( keyword_any );
            any { $_ == 500 } @ary;
        },
        list_util_any => sub {
            List::Util::any { $_ == 500 } @ary;
        },
    })
);
print "\n";

say "keywordのほうがパフォーマンスが良い場合(all)";
cmpthese(
    timethese(-1, +{
        keyword_all => sub {
            use experimental qw( keyword_all );
            all { $_ < 500 } @ary;
        },
        list_util_all => sub {
            List::Util::all { $_ < 500 } @ary;
        },
    })
);
print "\n";

say "List::Utilのほうがパフォーマンスが良い場合(any)";
cmpthese(
    timethese(-1, +{
        keyword_any => sub {
            use experimental qw( keyword_any );
            any { $_ == @ary / 2 } @ary;
        },
        list_util_any => sub {
            List::Util::any { $_ == @ary / 2 } @ary;
        },
    })
);
print "\n";

say "List::Utilのほうがパフォーマンスが良い場合(all)";
cmpthese(
    timethese(-1, +{
        keyword_all => sub {
            use experimental qw( keyword_all );
            all { $_ < @ary / 2 } @ary;
        },
        list_util_all => sub {
            List::Util::all { $_ < @ary / 2 } @ary;
        },
    })
);
print "\n";

レキシカルなメソッドを宣言できるようになった

my sub のように my method でスコープ内でのみ呼び出すことのできる、レキシカルなメソッドを宣言できるようになりました。
また、レキシカルメソッドを呼び出すための演算子 ->& も追加されました。

use v5.42;
use experimental 'class';

class Point {

    my method hoge {
        say "hoge";
    }

    method wrap {
        $self->&hoge;
    }

}

Point->new->wrap(); # hoge

$self->&methodmethod($self) の糖衣構文です。
同じクラス内でもスコープが違えば呼び出すことはできないですし、継承先のクラスから呼び出すこともできません。

switch 機能とスマートマッチング演算子の削除が無期限の延期に

Perl5.38 で非推奨となり、5.42で削除予定だったswitch 機能(given-when構文)とスマートマッチング演算子は削除が無期限に延期となり、これらを使っても実験的機能であることの警告は発生しないようになりました。

switch機能はデフォルトでは無効になっており、個別に有効にするか、v5.34 までの feature bundle で有効になります。 v5.35 以降の feature bundle では無効になります。

{
    use v5.10;
    given (100) {
        when ($_ % 2 == 0) {
            print "$_ is even";
        }
        default {
            print "$_ is odd";
        }
    }
}

{
    use v5.36;
    given (100) { # syntax error
        when ($_ % 2 == 0) {
            print "$_ is even";
        }
        default {
            print "$_ is odd";
        }
    }
}

{
    use feature 'switch';
    given (100) {
        when ($_ % 2 == 0) {
            print "$_ is even";
        }
        default {
            print "$_ is odd";
        }
    }
}

スマートマッチングはデフォルトで有効ですが、 smartmatch 機能として有効/無効を切り替えられるようになりました。 v5.40 までの feature bundle では有効になっており、v5.42 以降では無効となります。

{
    use v5.41;
    print 'A' ~~ ['A' .. 'D'] ? 'Included' : 'Not included'; # syntax error
}

{
    use feature 'smartmatch';
    print 'A' ~~ ['A' .. 'D'] ? 'Included' : 'Not included';
}

あくまで後方互換性のためのことを考えると削除が難しかったので残しておいている、という感じがするのでこれからswitch機能やスマートマッチングを多用するコードを書くのはおすすめできないです。 特にスマートマッチングはオペランドごとの挙動を覚えるのが難しいのでやめておいたほうが良いでしょう。

switch機能は直接条件式を記述するなどスマートマッチングを利用しないように使う限りにおいては使用しても問題ないかなと思いますが、 だとしても代替としてmatch構文が提案されており、実験的実装も作られ後々実装される可能性があるので、今まで通りコードを書くのが一番無難かなと思います。

フィールド変数の attribute :writer が追加

クラス構文のフィールド変数の値を更新する setter を自動生成する attribute が追加されました。 スカラ変数のみ対応しています。

class Point {
    field $x :writer :param;
    field $y :writer :param;
}

my $p = Point->new( x => 20, y => 40 );
$p->set_x(60)->set_y(100); # writerはインスタンス自身を返すのでメソッドチェーンも可能

引数を指定すると指定した名前で setter を生成します。

Moose系のクラスビルダーのアクセサと違ってフィールド名と同名のアクセサで getter / setter 両方として使えるようにできないので注意が必要です。

パッケージの区切り文字としてのアポストロフィを無効にできるようになった

Perlではパッケージ名の区切り文字に :: を利用しますが、Perl4の頃は ' を利用しており、その互換性を保つためPerl5になってからもずっと ' をパッケージ名の区切り文字として利用できるようになっていました。
' をパッケージ名の区切り文字として利用することは Perl5.38 で非推奨となり、 Perl5.41.3 で一旦削除されましたが、議論の後にデフォルトでは復活し、プラグマで有効/無効を切り替えられるようになりました。

以下のように apostrophe_as_package_separator 機能として有効/無効を切り替えられるようになっています。

use feature 'say';
use POSIX;

use feature 'apostrophe_as_package_separator';

say $POSIX'VERSION; # $POSIX::VERSION と同じ

no feature 'apostrophe_as_package_separator';

say $POSIX'VERSION; # コンパイルエラー

apostrophe_as_package_separatoruse v5.42 で無効になります。

use v5.42;
say $POSIX'VERSION; # コンパイルエラー

chdir が CORE:: 名前空間に追加された

コア関数と同名の関数がパッケージ内に定義されている際、曖昧さを避けて呼べるよう CORE:: にいくつか組み込み関数が追加されていってるのですが、その流れの1つかと思われます。

二項演算子で左項が否定されるのが不自然な場合に警告が発生するように

!$x < $y のようなコードがあったとして、比較演算子で左項を本当に否定したいことはまずなくて、通常は条件式全体を否定したい場合が多いと思います。 そのような場合に警告が発生するようになりました。

!$x < $y # 警告が発生: Possible precedence problem between ! and numeric lt (<)

拘束演算子(=~など)、cmp<=> 以外の比較演算子、isa演算子でこの警告が発生するようです。

このような場合は否定された演算子を使うか、かっこで優先順位を明示するか、優先順位の低い論理否定演算子 not を利用するようにしましょう。

$x >= $y
!($x < $y)
not $x < $y

builtin モジュールの indexed 関数で配列のインデックスと値の組のリストを生成し、2変数のforループでイテレーションするコードのパフォーマンスが改善

Perl5.40で追加された、組み込み関数を提供する builtin モジュールの indexed 関数を利用することで、配列のindexと要素の列挙が楽に書けるようになっていました。

use v5.40;

my @array = qw( red blue green );
for my ($index, $value) (indexed @array) {
    say "$index => $value";
}

ただし、これは配列のインデックスと値のリストを実際に生成する点など、通常の for (@array) のようなループ文と比べて効率的でないコードとなっていました。
5.42からは内部的には配列のインデックスと値のリストを実際に生成するのではなく、通常の for (@array) と同じ方法で配列をイテレーションするようになりました。

まとめ

source::encoding プラグマが追加されことは影響が大きそうで、 use utf8 していないころに書かれたコードは見直したほうがいいかもしれません。 その他は今回も細かい改善点が多いといった感じですが、Perlに足りなかった機能が追加されたりPerl4のころの構文を無効にできるようになったりと確実に過去のバージョンより使いやすくなっています。 次のバージョンでは名前付き引数が追加されるなど、大きな変更がありそうで楽しみです。

この記事では書けなかったこともあるので詳しいことが気になった方は公式ドキュメントもぜひ読んでみてください。


  1. use v5.42; のような構文のことです。Perlでは後方互換性を保つため古い機能は無効にできるように、新しい機能は有効にできるようになっていますが、 feature bundle はそういった各機能を、バージョンごとに推奨されるものをまとめて有効/無効にしてくれるプラグマになっています。