Perlのレキシカル関数の使い所2選
この記事はPerl アドベントカレンダー 2023のN日目の記事です。娘はサンタさんにキックボードをねだっていました。
レキシカル関数というと、ブロックやファイルだったり特定のスコープでだけ有効な関数のことで、スコープ外では存在しないように扱えます。
Perlの場合、my sub SUBNAME { ... }
のように書くとレキシカル関数が定義できて、v5.26から正式機能になっています。
{ # このブロックの中だけ、helloは有効 my sub hello { "Hi!" } hello(); # Hi! } hello(); # ERROR: Undefined subroutine &main::hello
...が、無名関数を使えば用を足せると思って、機能が正式化してから6年経ちますが全然使ってなかったです。今は、めっちゃ便利じゃんと思い直したので、おすすめ?の使い方を書きます。
プライベートメソッドとして
プライベートメソッドとしての利用は、レキシカル関数のTHE・想定通りの使い方だと思います。Perlは慣習的に、メソッドを外部パッケージから呼び出さないように隠蔽したい時、「_private」のように関数名にアンダースコアをつけて「これはプライベートメソッドだから外から絶対呼び出さないでね!」と目印をつけ紳士協定で生活します。けど、ただの目印に過ぎないので、アンダースコアがついていようと呼び出せるわけで、当然?紳士協定をやぶる輩(自分含め)がでてきます。そんな紳士協定違反ができないコーディングパターンはあるにはありますが、自分はしっくりこなかったです。(my $private = sub { ...}
のように無名関数を利用したり、Class::InsideOutのようなinside-outパターンのOOPモジュールを利用など。紳士協定違反の予防と、ビルトインと異なるオブジェクトの記法を採用の2つを見比べると、バランスが悪いなーと)
けど、今はプライベートメソッドが欲しければ、既存の関数定義にちょっと足すだけで実現できます。
package Foo { # レキシカル関数を使って、外部から呼べないメソッドを定義 my sub private_method($class) { ... } sub public_method($class) { $class->private() # do something } } Foo->public_method; Foo->private_method; # ERROR: Undefined subroutine &Foo::private_method
ユニットテストを読みやすくするため
通常の関数定義だと、パッケージ内に同じ名前の関数を何個も作ることはできません。が、レキシカル関数であれば、パッケージ内に同じ名前の関数をいくつも定義できます。この性質が、ユニットテストと相性良きです。具体的にはこんな感じ。
use v5.38; use Test2::V0; subtest 'add' => sub { # add関数をテストする、テスト主題を用意 my sub subject($a, $b) { add($a, $b) } is subject(1, 2), 3; is subject(4, 5), 6; }; subtest 'baz' => sub { # 2個目のsubject 関数の定義だけど、1個目の定義とスコープが違うので定義できる my sub subject($a, $b) { ... } # 期待値と一致するか確認するのに記述が複雑な場合は、テストユーティリティを用意するのも手 my sub run_test($a, $b, $expected, $testname) { my $ctx = context(); my $result = subject($a, $b); is $result, object { prop blessed => 'Some::Result'; call value => $expected; }, $testname; $ctx->release; } run_test(1, 2 => 3, 'Case1'); run_test(4, 5 => 6, 'Case2'); }; done_testing;
このコードでは、テストの主題やテストユーティリティのレキシカル関数を定義していますが、テストの入力と期待値が明確になっているので良いなーと。subject
といったお決まりの用語を決めれば、テストの読み下しもしやすくなると思います。rspecっぽさがありますね。また、個人的に良いと思うのが、このコードは、言語のビルドインの機能だけで実現できてる点です。perlでrspecっぽく記述できるTest2::Tools::Specというフレームワークがありますが、こういったフレームワークの中身を熟読し、DSLの限界を探るなんてことは不要です。ただ、そこにレキシカル関数があるだけです。
さいごに
Perlのレキシカル関数の使い所を2つ紹介しました。3つ揃うと気持ちが良いので、他の使い所があれば、良かったらコメントで教えてください!