そのうちPerlに入るかもしれないcase文Syntax::Keyword::Matchの紹介

こんにちは。id:anatofuzです。 これはPerl Advent Calendar 2024の15日目の記事です。

昨日はid:mackee_wさんによるaquaでperlを入れて使えるようになりました でした。relocatable-perl自分は低スペックのPCでとりあえずPerl動かしたいときによく使っています。

さて、PerlC言語からの影響が強い言語ですが、C言語にあってPerlにない、というよりあったが使われてなかった機能にcase文があります。 かつてはPerlのコアにgiven-whenと呼ばれるcase文の機能がPerl5.10.1から導入されていました。

use v5.10.1;
given ($var) {
    when (/^abc/) { $abc = 1 }
    when (/^def/) { $def = 1 }
    when (/^xyz/) { $xyz = 1 }
    default       { $nothing = 1 }
}

これはgivenの後ろに評価したい変数をいれ、whenキーワードでいわゆるcaseに相当するものを書くスタイルです。 一見すると良さそうな機能なのですが、givenで値をスマートマッチ演算子(~~)を使って評価するという特徴があります。 このスマートマッチ演算子が曲者で、いくつかの問題を抱えていることから5.18で非推奨となりました。 http://perldoc.jp/docs/perl/5.18.0/perl5180delta.pod#The32smartmatch32family32of32features32are32now32experimental

長らく非推奨という形だったのですが、これを受けて5.41.3(Perlの開発用バージョン)でついにスマートマッチ演算子とgiven-whenが削除されました。 次のリリース予定の安定版(Perl5.42またはPerl42)でもスマートマッチ演算子とgiven-whenは削除されてリリースとなります。

metacpan.org

ということでコア機能からはcase文は消されたのですが、しかし全ての計算をif~elsifで書くのはちょっとだるい、というときが存在します。我々はcase文がやはり欲しくなるときがあるわけです。

さて、最近のCPANモジュールにはSyntax::Keyword名前空間のモジュールがいくつか存在します。 これはPaul Evans先生がPerlのsyntax plugin 機能を利用し構文の拡張を検証しているモジュール郡です。

PaulEvansはPerlのコア開発者でもあるので、Syntax::Keywordで成果がでた言語機能がPerl本体に取り込まれるというのが最近のムーブメントとなっています。具体的には5.36より導入されたdeferSyntax::Keyword::Deferの開発内容が元になっています。このためSyntax::Keyword名前空間のモジュールとPerlコアに最近入っている新機能は内容がコンパチであるので、Feature::Compat名前空間のモジュールを利用するとPerlバージョンに応じてコアかSynrax::Keywordかどちらかで処理を実行するという互換性に強いPerlアプリケーションを書くことができます。例えばdeferはFeature::Compat::Deferを使うとdeferがfeatureにある場合はfeature, ない場合はSyntax::Keywordなモジュールが使われるので、まずアプリケーション側をdefer対応してからPerlのバージョンアップ、というのが比較的スムーズに行えます。

さて、given-whenは消えてしまったわけですが、なんと今CPANにはSyntax::Keyword::Matchモジュールが公開されています。もちろんこれはPaulEvansによって作られた新しいcase文の検証実装です。

use v5.16;
use Syntax::Keyword::Match;
my $n = ...;
match($n : ==) {
   case(1) { say "It's one" }
   case(2) { say "It's two" }
   case(3) { say "It's three" }
   case(4), case(5)
           { say "It's four or five" }
   case if($n < 10)
           { say "It's less than ten" }
   default { say "It's something else" }
}

主な特徴はmatchキーワードの後ろに評価したい変数と演算子を指定します。 各case文はその演算子での比較先を書くことができます。この例では$nを数字として比較演算しているわけですね。 各caseブロックはgolangと同様に自動でbreakされます。cと違い明示的に書かなくても{}のブロック内で処理を完結できるので直感的ですね。複数の評価値で実行したいブロックをまとめたい場合は,で繋いで書くことで実装できます。

ここまでだと他の言語のcase文と同じなのですが、おもしろポイントとしてcaseの後ろにifを書くことで別の評価を行うことができます。上の例ではcase if ($n < 10)としていて、ここだけ大小演算が行われるわけですね。

他にはmatch構文のスコープだけで有効な変数も定義できます。例えばこの例では$xはcase文の中だけ使うことができる変数です。

match( my $x = some_function_call() : == ) {
   case ...
}

さてこう見ると結構使えそうな感じがありますね。実はこの前のISUCON14ではこっそりこのcase文を使ってみています。 一応導入前にif-elsifとのベンチマークの比較をしたのですが、ほぼ等価、またはcase文の方が多少早いという結果になったのでパフォーマンスも非常に良いです。 というわけである程度のアプリケーションでも実際にキビキビ動作するcase文、ぜひ使ってみてはどうでしょうか。

github.com

明日はid:karupaneruraさんでTBDです。お楽しみに!