Perl Hackers Hub

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

この記事を読むのに必要な時間:およそ 5 分

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;

著者プロフィール

石垣憲一(いしがきけんいち)

翻訳家兼プログラマ。歴史ネタ担当。最近は主にCPANツールチェーン界隈の片隅で活動中。

URL:http://d.hatena.ne.jp/charsbar/