この記事はPerl Advent Calendar 202518日目の記事です。
いつの間にか中置演算子を勝手に入れられるようになってたっぽい
一見するとPerlは原始時代から姿を変えていないシーラカンスのような言語だと思われるかもしれませんが、実は最近もいろいろ機能追加が続いています。しかし機能追加は安易にされているのではなく、ちゃんと検討や実験を経て追加されています。
実験ってなんぞやって思うかもしれませんが、実は今のPerlはかなり柔軟にキーワードなどを組み込める仕様になってまして。キーワードを追加するためのモジュールXS::Parse::KeywordのAuthorであるPaul Evansさんいわく

Perlの生きのこり - YAPC::Fukuoka 2025 - Speaker Deck
とのことなんで、Syntax::Keyword::**のようなモジュールをモリモリ作ってこうしたら言語が良くなるんじゃっていうのを示して言語に取り込んでもらうというサイクルが確立しているぽいです。
で、そう言うのを見てたら、XS::Parse::Infixと言うのを見つけました。あれ、中置演算子って追加できたっけと思いましたが、v5.38以降でこのモジュールは使えるみたいです。ほいならなんか面白いことできないかなと思ってこのブログ書いてます。
パイプライン演算子を追加してみよう
パイプライン演算子については以下の記事が詳しいです。
ある関数の結果を次の関数の引数に入れるみたいなやつです。Perlでやるとこんな感じかなあ。
[0..9] |> sub { [grep { $_ % 2 == 0 } $_[0]->@*] } |> sub { [map { $_ ** 2 } $_[0]->@*] }
これで0から9までの数字から偶数を取り出し、それぞれの数を2乗するような数が出せるはず。普通Perlで書くと逆で、
map { $_ ** 2 } grep { $_ % 2 == 0 } (0..9)
こうなります。前から後ろに書く方が他の言語だと一般的な感じがしますね。ではこういうふうに使える |> 演算子を作っていきましょう。
環境構築
最近はaquaを使うのがお気に入りです。aquaが入っている状態で、
$ aqua init $ aqua g -i skaji/relocatable-perl
これで最新のPerlが使えます。ビルド要らず!
XS::Parse::Infix::FromPerl
XS::Parse::InfixはXS、つまりPerlのC拡張として書かないといけないのがちょっとハードルが高いところです。しかしそんなあなたに朗報、このモジュールをPerlから扱えるようにしたXS::Parse::Infix::FromPerlがあります。今回はこれを使っていきます。
$ cpanm XS::Parse::Infix::FromPerl
それでは、書いていきましょう。
パイプライン演算子を使う
ここでは、pipeline.plというファイルを作成して以下のコードを書きます。
#!perl use v5.42; use utf8; use XS::Parse::Infix::FromPerl qw( register_xs_parse_infix XPI_CLS_ADD_MISC ); use Optree::Generate qw( make_entersub_op ); BEGIN { register_xs_parse_infix "|>" => ( cls => XPI_CLS_ADD_MISC, permit => sub { 1 }, new_op => sub { my (undef, $lhs, $rhs) = @_; return make_entersub_op($rhs, [$lhs]); }, ); }; use Data::Dumper; my $arr = [0..9] |> sub { [grep { $_ % 2 == 0 } $_[0]->@*] } |> sub { [map { $_ ** 2 } $_[0]->@*] }; warn Dumper $arr;
では解説していきましょう。
BEGIN { register_xs_parse_infix "|>" => ( cls => XPI_CLS_ADD_MISC, permit => sub { 1 }, new_op => sub { my (undef, $lhs, $rhs) = @_; return make_entersub_op($rhs, [$lhs]); }, ); };
解説するにしてもここしかないんですが。XS::Parse::Infix::FromPerlが提供する関数はregister_xs_parse_infixのみです。これで定義します。他のコードの実行より前に定義して組み込みたいので、BEGINフェーズで定義しています。
一つ目の引数は演算子自体ですね。次にclsですが、ここに定義されている定数を指定します。多分演算子の優先順位とかに関わるかと思います。
permitオプションは、この演算子を許可するかどうかです。一旦ここでは1を返して許可。
で、new_opなんですが、ここは演算子の挙動を記述します。返すのはoptreeです。optreeっていうのはPerlの抽象構文木みたいなやつですね。この無名関数にはいろいろ渡ってくるんですが、2つ目と3つ目に演算子の左と右が渡ってくるので、それを使っていろいろゴニョゴニョするのが基本ぽいです。
で、make_entersub_opですが、これはOptree::Generateモジュールが提供する、関数呼び出しのoptreeを生成するシンタックスシュガー的なやつです。この場合、演算子の右側$rhsにはcoderefがやってくる想定なので、これを引数の一つ目にして、2つ目はこの関数に渡す引数なので、演算子の左側を渡します。
これで実行すると、
$ $ perl pipeline.pl
$VAR1 = [
'0',
'4',
'16',
36,
'64'
];
ちゃんと動いてますね!
ちなみにスカラ以外の例えばリストなんかを渡すとセグフォするので、実用的にはもっと色々なケースを考えないといけなさそうです。