Perlのレキシカルサブルーチンとperlcritic2022年12月21日 02時48分

この記事はPerl Advent Calendar 2022の21日目の分です。


Perlでは、関数内で定義した関数も外部から見えてしまいます。

use feature 'say';

sub foo {
    sub bar {
        say 'bar';
    }
    bar();
}

# foo関数の外でもbar関数を呼び出せる。
bar();

特定のスコープでのみ参照できる関数を定義したいときは、関数定義をsubではなくmy sub(またはstate sub)から始めます。この機能はレキシカルサブルーチン(lexical subroutinesと呼ばれます。

use feature 'say';

sub foo {
    my sub bar {
        say 'bar';
    }
    bar();
}

# 未定義の関数呼び出しによる例外が発生する。
bar();

ちょっとした処理をまとめるのに便利なレキシカルサブルーチンですが、perlcriticとの組み合わせに難がありました。レキシカルサブルーチンを使ったコードをperlcriticにかけると、Subroutines::ProhibitNestedSubsポリシーSubroutines::ProhibitBuiltinHomonymsポリシーのエラーが出てしまうのです。(perlcriticはPerl向けのリンターです。詳しくは「perlcriticとのつきあい方 - 私が歌川です」などを参照してください。)

Subroutines::ProhibitNestedSubsポリシーは関数の入れ子を禁止します。入れ子の内側の関数が意図せず外部に公開されるのを避けるためのものなので、もともと外部に公開されないレキシカルサブルーチンに対しては禁止する意味がありません。

Subroutines::ProhibitBuiltinHomonymsポリシーは組み込み関数と同名の関数を禁止します。perlcriticの内部で使われるで使われるPPIモジュールの不具合により、レキシカルサブルーチンの名前は常にsubであるとみなされていました。

これらの問題を解決するため、昨年perlcriticPPIに以下のプルリクエストを提出しました。

この年末までにこれらの変更がすべて取り込まれたので、perlcritic(とPPI)のバージョンを最新にすれば、心置きなくレキシカルサブルーチンを使えます。