その手の平は尻もつかめるさ

ギジュツ的な事をメーンで書く予定です

Perl5のサブルーチン呼び出し時に二項演算子を期待していたら単項演算子の引数として認識されてしまう

sub func {
    return 100;
}

みたいなサブルーチンがあった時に,

say(func - 99);

みたいな感じで呼び出すと,パッと見 1 が出力されそうに見えるが,実際は 100 が出力される.
これは - 99func の引数として解釈されて食われるためである. B::Deparse するとわかりやすい.

$ perl -MO=Deparse func.pl
sub func {
    use warnings;
    use strict;
    use feature 'say';
    return 100;
}
use warnings;
use strict;
use feature 'say';
say func(-99);
func.pl syntax OK

ここから,二項演算子としての振る舞いを期待していた -99 と結合して単項演算子として扱われ, -99 が引数として扱われているのが読み取れる.

もちろん,サブルーチン呼び出し時にかっこを付けて

say(func() - 99);

という感じで呼び出してやれば当初の期待値である 1 が表示されるようになる.もしくはプロトタイプを利用する方法もある.

sub func () {
    return 100;
}

このように引数を取らないことをプロトタイプで明示すると,サブルーチン呼び出し時にかっこを付けなくても後続の値が引数として解釈されることはなくなる.その場合の B::Deparse の結果は以下のようになる.明解.

$ perl -MO=Deparse func.pl
sub func () {
    use warnings;
    use strict;
    use feature 'say';
    return 100;
}
use warnings;
use strict;
use feature 'say';
say func - 99;
func.pl syntax OK

これらは基本的な挙動ではあるのだけれど,日々を生きているとたまにハマりがち.例えば Time:PieceTime::Seconds をあわせて使ったときなんかに

use Time::Piece;
use Time::Seconds qw/ONE_HOUR/;
my $t = localtime - ONE_HOUR;
say $t;

などと書こうものなら,結果が Thu Jan 1 08:00:00 1970 というふうになり破滅する.localtime(-ONE_HOUR) となるからだ.
普段,perlの組み込み関数の時は (基本的に) かっこを付けずに記述するスタイルで書いていたので,組み込み関数である localtime をその感覚で使っていたところ綺麗にハマってしまった. *1


いままでは割と野生の勘みたいな感じでこのへんの諸問題を回避していて,「そういう掟なのじゃ」という感じで生活していたんですが真面目に B::Deparse とかで調べてみると色々学びがあるものですね.

*1:Time::Pieceはlocaltimeをoverrideするから最早組み込み関数ではないんでは? という意見はあると思いますが