Perl Hackers Hub

第46回Perl 5.26で変わること(3)

(1)こちら⁠2)こちらから。

Perl 5.26で新たに追加されたもの

このように非互換性の話ばかりしているとPerl 5.26へのアップグレードをためらう声も出てきそうですが、Perl 5.26にはセキュリティ上のメリットだけでなく、性能的なメリットや便利な新機能などもあります。

インデント付きヒアドキュメント

ある程度まとまった分量の、特に複数行にわたる文字列を扱いたい場合は、ヒアドキュメントを利用するのが定番です。

print <<"END";
$name さん、こんにちは。
...
END

ただ、従来のヒアドキュメントはインデントの概念が存在していなかったため、メソッドや条件文に囲まれていると見栄えが悪くなる欠点がありました。

sub print_hello {
    my $name = shift;
    if ($name) {
        print <<"END";
$name さん、こんにちは。
...
END
    }
}

Perl 5.26では新たにインデント付きのヒアドキュメントが導入されました。これを使うと、範囲内のすべての行が終端文字列のインデントに合わせてインデントされているとみなされます。

sub print_hello {
    my $name = shift;
    if ($name) {
        # 出力される内容は前と同じ
        print <<~"END";
            $name さん、こんにちは。
            ...
            END
    }
}

コンフリクトを起こしているソースコードの検出

Gitなどのバージョン管理システムを利用して開発を進めていると、ときにはコンフリクトを起こしてしまうことがあります。もちろんそのようなコンフリクトはすぐに解消すべきものですが、うっかりしているとコンフリクトが残ったままテストを走らせてしまうことがあるものです。

<<<<<<< HEAD
print "Hello, World!";
=======
print "Goodbye, World!";
>>>>>>> test

従来のPerlは、このようなソースコードを読み込むと暗黙のうちに空の終端文字列を持つヒアドキュメントとみなして解釈していたのですが、Perl 5.26では正しくバージョン管理システムのマークを読み取って、次のようなエラーメッセージを出すようになりました。

Version control conflict marker at test.pl line 1, near
"<<<<<<<"
Version control conflict marker at test.pl line 3, near
"======="
Version control conflict marker at test.pl line 5, near
">>>>>>>"

Unicode 9.0サポート

Perl 5.26ではUnicode 9.0がサポートされています。Unicode 9.0にはオリンピックで活躍するであろう金銀銅メダルの絵文字などが追加されました。

/xx正規表現修飾子

従来からある/x正規表現修飾子を使うと、空白文字の扱いが変わることを代償に、正規表現中に改行やコメントを追加して読みやすくできます。ただし、/x修飾子は文字クラスの中身までは対象にしません。そのため、次のようなコードは、与えられた文字列によっては一見うまく動いているように見えますが、対象となる文字列に空白が含まれている場合、その空白も一緒に抜き出してしまいます。

my $string = "Hello, World!";
my @words = $string =~ / ([ a-z A-Z ]+) /gx;
say join ";", @words; # Hello; World

Perl 5.26で導入された/xx修飾子を使うと、このような文字クラス内の空白とタブも(バックスラッシュでエスケープされていない限り)無視できます。

my $string = "Hello, World!";
my @words = $string =~ / ([ a-z A-Z ]+) /gxx;
say join ";", @words; # Hello;World

特殊変数@{^CAPTURE}など

マッチしても、しなくてもよいという正規表現で、実際に何がマッチしたかを調べるのは意外と面倒なものです。

たとえば、⁠a: b c」というコロンと空白で3つの部分に区切られた文字列からデータを取り出すことを考えてみます。あまり細かいことを考えずに書けば、次のようなコードになるでしょうか。

use 5.010;
my $string = "a: b c";
$string =~ /\A(.+?): (.+?) (.+)\z/;
say "$1, $2, $3"; # a, b, c

今、仕様が変わって3つ目の部分には値が入らない場合があることがわかりました。正規表現の最後を調整しましょう。

my $string = "a: b";
$string =~ /\A(.+?): (.+?)(?: (.+))?\z/;
say "$1, $2, $3"; # a, b,

出力結果の最後に余計な,が付いているのは見栄えが良くないですし、warningsプラグマを有効にしていると警告が出るのもいただけません。

このような場合、$3の値が定義されているかどうかで挙動を変えてもよいのですが、$0から始まる特殊変数が何文字目から始まり何文字目で終わるという情報を格納している特殊変数@-@+を見ると、いくつマッチしたかを調べられます[8]⁠。また、マッチした値はsubstr関数を使って導き出せます。

my $string = "a: b";
$string =~ /\A(.+?): (.+?)(?: (.+))?\z/;
say $#-; # 2
my $first = substr($string, $-[1], $+[1] - $-[1]); # $1

検索の場合はマッチした値を直接配列で受け取ることもできますが、この受け取り方は必ずしも期待どおりの値を受け取れるとは限りません。

my $string = "a: b";
my @capture = $string =~ /\A(.+?): (.+?)(?: (.+))?\z/;
say join ", ", @capture; # a, b,

Perl 5.26で新設された@{^CAPTURE}特殊変数を使うと、この処理をもっと直感的に書けます。

use 5.026;
my $string = "a: b";
$string =~ /\A(.+?): (.+?)(?: (.+))?\z/;
say scalar @{^CAPTURE}; # 2
say join ", ", @{^CAPTURE}; # a, b
say ${^CAPTURE}[0]; # a
# この特殊変数には1つバグがあるが
# 次のバージョンで修正される見込み
say "${^CAPTURE}[0]"; # [0]

同様に、名前付きキャプチャについては@-@+に対応する特殊変数%-%+を駆使する代わりに、Perl 5.26で新設された%{^CAPTURE}%{^CAPTURE_ALL}という特殊変数を使うと同じようなことができます。

use 5.026;
my $string = "a: b";
$string =~ /\A(?<A>.+?): (?<B>.+?)(?: (?<C>.+))?\z/;
say scalar %{^CAPTURE}; # 2
say join ", ", sort keys %{^CAPTURE}; # A, B
say ${^CAPTURE}{A}; # a

scalar %hashが数値のみを返すように

ハッシュをスカラコンテキストで評価すると、従来は2/8のようにキーの数と内部的に割り当てられたバケツの数をスラッシュでつないだ値が返ってきました。Perl 5.26からは、上の例でも見たようにキーの数だけを返します。

use 5.026;
my %hash = (A => "A", B => "B");
say scalar %hash; # 2

レキシカルサブルーチンが正式機能に昇格

あまり行儀の良いことではありませんが、グロブとコードリファレンスを使うと、特定のブロックの中でだけ関数の挙動を変えることができます。テストやデバッグの際には重宝しますが、サブルーチンを再定義することになるので、従来はno warningsを使って警告を抑制する必要がありました。

use 5.010;
use strict;
use warnings;
use Test::More;
ok 1;
{
    no warnings "redefine";
    local *ok = sub {
        my ($package, $file, $line) = caller;
        Test::More::ok($_[0], "LINE: $line");
    };
    ok 2; # ここでは行番号が出力される
    ok 3; # 同じく
}
ok 4; # 行番号は出力されない
done_testing;

このような処理は、Perl 5.18で実験的に導入されたレキシカルサブルーチンを使うともう少しきれいに書けることがあります。レキシカルサブルーチンを使えば再定義の警告を抑制するno warningsも、サブルーチン定義のあとのセミコロンも必要ありません。

use 5.018;
use strict;
use warnings;
use feature "lexical_subs";
no warnings "experimental::lexical_subs";
use Test::More;
ok 1;
{
    my sub ok {
        my ($package, $file, $line) = caller;
        Test::More::ok($_[0], "LINE: $line");
    }
    ok 2; # ここでは行番号が出力される
    ok 3; # 同じく
}
ok 4; # 行番号は出力されない
done_testing;

ただし、レキシカルサブルーチンを利用するには、上の例にあるようにuse featureを使って明示的にレキシカルサブルーチンを使うことを宣言し、実験的機能を使っている警告を抑制する必要がありました。

この部分はPerl 5.20で導入されたexperimentalプラグマを使うことで1行にまとめられます。ただ、experimentalプラグマには、MooseモジュールやRole::Tinyモジュールのように内部的にすべての警告を有効にしてしまうモジュールをあとから呼び出すと、隠したはずの警告が表示されてしまう罠がありま す。

use 5.020;
use strict;
use warnings;
use experimental "lexical_subs";
# このあとでMooseなどを呼ぶと警告が復活するので
# use experimentalはなるべく最後に回す
...

Perl 5.26ではレキシカルサブルーチンが正式な機能に昇格したため、そのような制約はなくなります。

レキシカルサブルーチンの罠

なお、パッケージ名の付いた完全修飾名のサブルーチンをレキシカルにすることはできません。そのため、上の例のようにエクスポートされた関数をレキシカルサブルーチンで上書きすることはできますが、モジュールの中に外から手を入れる用途では使えません。

local *Test::More::ok = sub { ...}; # OK
my sub Test::More::ok { ... } # エラー

また、通常の名前付きサブルーチンとは異なり、レキシカルサブルーチンは動的に生成されるため、置く場所によっては異なる挙動を示すことがあります。たとえば次のコードはlog関数が意図どおりに上書きされて、引数に現在時刻を付けたものが出力されます。

use 5.026;
package Batch {
    my sub log { localtime.": $_[0]" }
    sub run { say log(10) }
}
Batch->run;

次のようにすると、log関数は上書きされず対数が出力されます。

use 5.026;
package Batch {
    sub run { say log(10) }
    my sub log { localtime.": $_[0]" }
}
Batch->run;

サブルーチンシグネチャの高速化

Perl 5.20に鳴り物入りで導入されたサブルーチンシグネチャですが、当初の実装は残念ながら従来の書き方に比べてかなり遅いものでした。試しに次のようなコードでベンチマークを取ってみます。

bench.pl
use 5.020;
use experimental "signatures";
use Benchmark qw/cmpthese/;

cmpthese(10000000, {
    no_signature => sub { no_sig(1, "value") },
    signature => sub { sig(1, "value") },
});

sub no_sig {
    my ($num, $str) = @_;
    return "$num $str";
}

sub sig ($num, $str) {
    return "$num $str";
}

Perl 5.20で実行すると、手もとの環境では従来のコードのほうが50%近く高速でした。

$ plenv local 5.20.0 && perl bench.pl
                  Rate    signature no_signature
signature    1261034/s           --         -35%
no_signature 1926782/s          53%           --

この傾向はPerl 5.22でも変わりません。

$ plenv local 5.22.0 && perl bench.pl
               Rate       signature no_signature
signature    1218027/s           --         -37%
no_signature 1945525/s          60%           --

Perl 5.24でも傾向は同じですが、このとき行われた最適化の結果、関数呼び出しが以前に比べて全体的に高速になっています。

$ plenv local 5.24.0 && perl bench.pl
                  Rate signature no_signature
signature    1461988/s        --         -32%
no_signature 2164502/s       48%           --

Perl 5.26ではこの差がかなり縮んでいます。

$ plenv local 5.26.0 && perl bench.pl
                  Rate    signature no_signature
signature    2020202/s           --          -6%
no_signature 2155172/s           7%           --

残念ながらまだ逆転とまではいきませんが、Perl 5.28ではさらに最適化が進むものと期待されています。

機能の廃止や削除の予定

Perlの原作者のLarry Wall氏がPerl 6の開発に専念するようになってからというもの、YAPCなどのカンファレンスではその年にリリースされたPerl 5の安定版や、現在まさに開発中の次期バージョンについての話はあっても、Perl 5は今後こうなっていく、という長期的な展望はあまり語られないのが常でした。

その現状は今も変わってはいませんが、特定機能の廃止や削除については、直近の安定版で廃止の手続きに入ったことを知らせる警告を出すよう修正したあと、問題が見つからなければ2つあとの安定版で削除する(エラーとして扱われるようになる)のが現在の開発ポリシーとなっています。そのため、廃止の手続きに入った時期がわかれば、いつ実際に消えるかが予想できます。

Perl 5.26では、ユーザーが中長期的な変更に備えられるよう、新たにperldeprecationという文書を用意して、この先数年でどのような機能が消えることになるかを明記するようになりました。現時点では3年後にリリースが予定されているPerl 5.32までの廃止、削除予定がまとめられています。

まとめ

Perl 5.26ではセキュリティ上の理由から@INCからカレントディレクトリが削除されるなど、スムーズな移行を妨げかねない修正がいくつか入っています。しばらくは古いPerlで様子見せざるを得ない場合もあるかと思いますが、現在のPerl 5は最新2つの安定版(本稿執筆時点においてはPerl 5.26とPerl 5.24)しかサポート対象としないことがポリシーとして明記されていますし、最近の傾向として(特に内部の整理や高速化の代償として)細かな機能の廃止や削除も続いています。実際に5.26を本番投入するかどうかはさておき、可能ならCIサービスなどを利用して、お手もとのモジュールやアプリケーションのテストが一通り通ることだけでも確認しておいていただければと思います。

さて、次回の執筆者は佐藤健太さんで、テーマは「Anikiで学ぶ実践的なO/Rマッパの作り方」です。お楽しみに。

WEB+DB PRESS

本誌最新号をチェック!
WEB+DB PRESS Vol.130

2022年8月24日発売
B5判/168ページ
定価1,628円
(本体1,480円+税10%)
ISBN978-4-297-13000-8

  • 特集1
    イミュータブルデータモデルで始める
    実践データモデリング

    業務の複雑さをシンプルに表現!
  • 特集2
    いまはじめるFlutter
    iOS/Android両対応アプリを開発してみよう
  • 特集3
    作って学ぶWeb3
    ブロックチェーン、スマートコントラクト、NFT

おすすめ記事

記事・ニュース一覧