Masteries

技術的なことや仕事に関することを書いていきます.

Perlの未定義動作100連発

PHPカンファレンス福岡2023に参加して高まってきたので, Perlの未定義動作100連発というエントリを書きます. なおperldocと実際の挙動については, 5.36.0のものを利用しています.

その1: スカラコンテキストでsort

sort はリストの中身をソートするので, 当然(ソートした結果を受け取るために)リストコンテキストで呼び出す必要があります. 「そんなことしないだろ...」と思いますが, もしスカラコンテキストで呼び出した場合の振る舞いは未定義です.

perldoc.jp

スカラコンテキストでは、sort の振る舞いは未定義です。

実際にやってみました:

コード

use strict;
use warnings;
use DDP;

my $sorted = sort { $a <=> $b } (1, 3, 2, 4);
p $sorted;

結果

Useless use of sort in scalar context at if.pl line 5.
undef

なんとなく, 「ソートした結果の先頭が入ってくるのかな...?」と思っていたけど, undef になるようでした. 「Useless use of sort in scalar context」という警告も出てきますし, まあこういうコードを書いてしまってそのままにしてしまう, ということは少ないでしょう.

その2: my + 後置if

詳しくは id:t_kyt *1さんの以下のエントリを読んでください:

developer.hatenastaff.com

perldoc.jp

注意: (my $x if ... のような) 条件構造やループ構造で修飾された my state,our 文の振る舞いは 未定義 です。 my 変数の値は undef かも知れませんし、以前に代入された値かも 知れませんし、その他の如何なる値の可能性もあります。 この値に依存してはいけません。 perl の将来のバージョンでは現在のバージョンとは何か違うかも知れません。 ここには厄介なものがいます。

実際にやってみました:

コード

use strict;
use warnings;
use DDP;

my $num1 = 123 if 1;
my $num2 = 345 if 0;

p $num1;
p $num2;

結果

123
undef

未定義動作ではありますが, 「まあそうなるよね」という結果になりました. とはいえ, 未定義動作なのでmyと後置ifは組み合わせないようにしましょう. ちなみに, 代入のない単なるmyに後置ifを書いた場合は次のようなエラーになります:

コード

use strict;
use warnings;
use DDP;

my $num1 if 1;

結果

This use of my() in false conditional is no longer allowed at if.pl line 6.

その3: forループの返り値

「forループの返り値...?」となりますよね. まずは perldoc を見てみましょう:

perldoc.jp

洞察力のある Perl ハッカーは、for ループに返り値があり、この値は ループを do ブロックで包むことによって捕捉できることに 気付くかもしれません。 この発見に対する報奨は、この警告的なアドバイスです: for ループの返り値は 未定義で、予告なしに変更されることがあります。 これに依存してはいけません。

というわけで, forをdoで包んで, 強引に返り値を取得してみましょう:

コード

use strict;
use warnings;
use DDP;

my $ret = do {
    for (1..10) {
        # ...
    }
};
p $ret;

結果

""

...というわけで, 強引にforの返り値を取得すると空文字列が返る, みたいです. 「forをdoで囲むことはないやろ...?」と思われるかもしれませんが, 次のコードは有り得そうです:

コード

use strict;
use warnings;
use DDP;

my $ret = func();
p $ret;

sub func {
    for (1..10) {
        # ...
    }
}

結果

""

この場合, 関数funcにはreturnがないので, 関数の返り値として一番最後に評価されるforの返り値が使われます. もし何かしらの理由で返り値を利用することになった場合, 意図せず空文字が返ってきてびっくり!!! ということになりそうです. こういう事例を防ぐためにも, 明示的にreturnを書くのがいいな, と最近思いつつあります.

コード

use strict;
use warnings;
use DDP;

my $ret = func();
p $ret;

sub func {
    for (1..10) {
        # ...
    }
    return undef;
}

結果

undef

いかがでしたか?

100連発といいつつ, まずは3連発でご紹介しました. Perlの未定義動作については, 今後また折を見て紹介していこうと思います.

*1:2023/06/28: 公開時, 記事の投稿者を誤って id:akiym さんと記載していました. お詫びして訂正いたします.