Hatena Developer Blog

はてな開発者ブログ

【2016年度版】このPerlハマりどころがすごい!100連発

はじめに

こんにちは。アプリケーションエンジニアの id:t_kyt です。

春です。Perlを始めるにはいい季節ですね。Perl始めていますか?

どの言語にもハマりどころというものがありますがPerlも例外ではありません。というわけで今回はPerlを始めた人がハマりがちなポイントを幾つか紹介したいと思います。

この記事ははてなの教科書程度の知識を前提としています。またモジュールに依存しない部分に絞りました。

github.com

autovivificationを避ける

autovivificationとはundefの入った変数をハッシュ(や配列)としてdereferenceしたとき、ハッシュの構造を自動で作ってくれる機能です。 具体例を見たほうが早いでしょう。

use strict;
use warnings;

use Data::Dumper;

my $h;

$h->{foo}->{bar}->[2] = "ほげだよ!!";

print Dumper $h;
% perl t.pl
$VAR1 = {
          'foo' => {
                     'bar' => [
                                undef,
                                undef,
                                'ほげだよ!!'
                              ]
                   }
        };

$h は宣言だけして初期化は行われていないのでundefのはずですが、いい感じにハッシュを作ってくれます。 一見便利機能に見えますが、暗黙的に何かする機能というのは大抵はハマりどころになるものです。使わなくても良いのであれば使わない方がいいでしょう。

また、これは一般的に言われる話ですが宣言と初期化は同時に行ったほうが良いです。特にPerlは代入されるまでその変数の型(数値なのか、あるクラスのインスタンスなのか等)の情報を得ることができないため、なおさら同時に行ったほうが良いでしょう。

複雑な構造のハッシュを作りたい時はmapをつかうと大体のケースでは宣言と初期化を同時に行えると思います。

use strict;
use warnings;

my $items = [
    {
        id   => 1,
        name  => 'a',
        color => 'white',
    },
    {
        id   => 2,
        name  => 'b',
        color => 'blue',
    },
];

my $map = { map {
    my $item = $_;

    ($item->{id} => {
        name  => $item->{name},
        color => $item->{color},
    });
} @$items };

use Data::Dumper;
print Dumper $map;
% perl t.pl
$VAR1 = {
          '1' => {
                   'color' => 'white',
                   'name' => 'a'
                 },
          '2' => {
                   'name' => 'b',
                   'color' => 'blue'
                 }
        };

PerlのmapはBLOCK節の最後に評価された値が返ります。 少し横道にズレますが、最後に評価された値が返るのはdo { };も同様なので、スコープを区切りたい時

my $hoge = do {
    なんか
    ...
    $some_value
};

などもよく行います。

myと後置ifを同時に使ってはならない

my $hoge = 'ほげだよ!!!' if $foo;

この時$hogeの値は何でしょうか?正解はわからないです。

perldocより引用します。 http://perldoc.perl.org/perlsyn.html

NOTE: The behaviour of a my, state, or our modified with a statement modifier conditional or loop construct (for example, my $x if ... ) is undefined.

未定義な振る舞いですが構文エラーにはならないのでうっかりやってしまわないように気をつけましょう。

return;で返る値は空のリスト

例えば以下の様な関数があったとします。

use strict;
use warnings;

sub hoge {
    return;
}

このhoge()が返す値は()(空のリスト)です。

この仕様がどういう時嬉しいかというとどちらのコンテキストの場合でも偽となる値が返ることです。

use strict;
use warnings;

sub hoge {
    return;
}

my $hoge_1 = hoge();
unless ($hoge_1) {
    print '$hoge_1は偽' . "\n";
}

my @hoge_2 = hoge();
unless (@hoge_2) {
    print '$hoge_2は偽' . "\n";
}
% perl t.pl
$hoge_1は偽
$hoge_2は偽

このようにどちらのコンテキストで評価されても偽として評価される値になるので、「失敗」を表したいときはreturn;で返り値を返すのでベストプラクティスとされています。(Perlベストプラクティスより*1

しかし単にreturn;しているとハマる場合があります。例えば以下のような場合です。省略のために無名関数を使っていますがsub hoge() {}のように宣言した場合でも同じです。

{
    hoge => sub { return }->(),
    fuga => sub { return 1 }->(),
}

このハッシュは

$VAR1 = {
          'hoge' => undef,
          'fuga' => 1
        };

になることを期待したいところです。 ですが、実際は

Odd number of elements in anonymous hash at t.pl line 7.
$VAR1 = {
          'hoge' => 'fuga',
          '1' => undef
        };

の様になり「意図しない」値になってしまいます。

この場合

{
    hoge => sub { return undef }->(),
    fuga => sub { return 1 }->(),
}

の用に明示的にreturn undef;を書くと、「意図した」ハッシュになります。

難しいですね。実を言うと僕もこの記事のチェックをしてもらっている際 id:Songmu id:nanto_vi の両名に間違いを指摘されました。

正規表現によるバリデーションでは\A\zを使おう

これはperlに限った話ではないが^$はそれぞれ行頭行末を表すので文字列の先頭と末尾にマッチさせるために使うには不適切です。特にmをつけた複数行モードの場合は要注意です。

詳しくは以下を参照してください。

正規表現によるバリデーションでは ^ と $ ではなく \A と \z を使おう | 徳丸浩の日記

'0' は偽で評価される

Perlの0は偽として評価されますが、'0'(文字列の0)も偽として評価されます。

つまり

print '偽です' unless 0;
print '偽です' unless '0';

このどちらも偽ですと出力されます。

これがどういう時に困るかというと、例えばユーザーが任意の自己紹介文を書いているか未入力かで処理を変えたい場合にif $text で判定すると0と入力されていた場合、未入力と判断されてしまいます。定義されているかはdefined、文字列の長さを知りたいならばlengthを使い判定しましょう。

余談ですがPerlでは数値の比較演算子と文字列の関係演算子は分かれています。 数値の場合

if ($a == $b) { なんか }

文字列の場合

if ($a eq $b) { なんか }

警告は出ますが間違いやすいので気を付けましょう。

each は中断した時、中断した時点の状態が残り続ける

Perlのeachは配列のvalueとともにindexも知りたい時に便利です。

use strict;
use warnings;
my @array = ('友奈', '美森', '風', '樹', '夏凜');

while (my ($index, $value) = each @array) {
    print $index, $value, "\n";
}
% perl t.pl
0友奈
1美森
234夏凜

ただしループ中に何らかの理由で中断された場合、あまり直感的ではない動作をします。

use strict;
use warnings;
my @array = ('友奈', '美森', '風', '樹', '夏凜');

while (my ($index, $value) = each @array) {
    last if $value eq '美森';
}

while (my ($index, $value) = each @array) {
    print $index, $value, "\n";
}
% perl t.pl
234夏凜

ループは最初から始まるとおもいきや、中断された時点から再開されます。

そういうものと割り切れば問題ないですが、知らないとハマりどころになるので気を付けましょう。

indexも欲しい場合は

use strict;
use warnings;
my @array = ('友奈', '美森', '風', '樹', '夏凜');

for my $index (0..$#array) {
    my $value = $array[$index];
    print $index, $value, "\n";
}

のように書くとハマりどころがなさそうです。

おわりに

「俺のほうがもっといい100連発できる」「いや、まだまだ落とし穴あるだろ……。」と思ったあなた!!一緒にPerlのハマりポイントについて書きませんか?ハマりどころでなくてもこんな便利モジュールがある等、春からPerlを始めた人に役立つ情報を募集しています!!!

あとエンジニアも絶賛募集中です!!!!!!!!!!!!!

*1:もちろんPerlベストプラクティスが常に正しい訳ではありません