Akihiro's Programmer Blog

Technology Notes for Personal

Perlを分かってそうで分かってない人が陥るアンチパターン


 この記事は Perl Advent Calendar 2014 の 15日目 の記事です。

 14日目の記事は karupanerura さんの Carton時代の必須インストールモジュール(Webアプリ編) でした。


目次


はじめに

 はじめに言っておきますが、Perlを分かってそうで分かってない人 = です。

 この記事では、Perlの扱いにある程度慣れてきたけれど、実はあんまり理解せず使っています、という方が陥るであろうミスをアンチパターン形式で紹介する。と見せかけて僕の恥ずかしい失敗というか、勘違いを書いていきます。


$classと$self

 Perlメソッドを定義するとき、私は全てこういう風に書き始めていました。

sub method {
    my $class = shift;

    # 適当な処理
}


 しかし、こういう風な書き方も見る事がありました。

sub method {
    my $self = shift;

    # 適当な処理
}


 この$classと$self(もしくは$this)の違いは何なのか?

 違和感は感じつつも、ただの好みの違いなのかなとスルーしていました。

 ところがドッコイ、キチンと意味はありました。


 これはつまり、クラスメソッドインスタンスメソッドかの違いを明示的にするためらしいのです。$classクラスメソッド$selfインスタンスメソッドです。

 クラスメソッドの場合、$classはクラスの名前、つまりパッケージ名を期待しています。

 対してインスタンスメソッドの場合、$selfはこのメソッドを起動したオブジェクトのリファレンスを期待しています。


 ですので、メソッドの定義をする場合は、そのメソッドの使い方と用途等を理解し、読み手に伝わるように書くと良い。ということですね。


mapの使い所

 例えば、ltsv形式の文字列をハッシュに変換したいというような場合に、数ヶ月前はforで回したりしていました。

my $ltsv = "host:127.0.0.1 user:hoge   status:200";

my %hash;
for my $element (split /\t/, $ltsv) {
    my ($key, $value) = split(/:/, $element, 2);

    $hash{$key} = $value;
}


 ですが、Perlにはmapという大変便利なものがございます。

my $ltsv = "host:127.0.0.1 user:hoge   status:200";

my %hash = (
    map { split(/:/, $_, 2) } split(/\t/, $ltsv)
);


 こいつは良い!簡潔に書ける!ということで、コード内でfor文が出てきたら、すかさずmapで書き直せないか考えるようになりました。

 一見良いことに思うのですが、これが良くない考え方になってしまっていました。

 例えば以下の様な場合です。

map { delete $hash{$_} if $_ eq 'host' } keys %hash;


 hashの特定のkeyの要素を取り除きたい、という場合です。

 良くない点としては、mapをデータ変換の目的で使用していないことが挙げられます。

 これなら以下のように、素直にfor文を使ったほうが可読性の面でも良いです。

for my $key (keys %hash) {
    delete $hash{$key} if $key eq 'host';
}


 mapには、「ここでデータ変換してますよ」と明示できる点も利点としてあるはずなので、むやみやたらに使うのではなく、その目的にあった手段を使うべき、ということですね。


doブロック内でreturn

 Perlにはdoブロックというものがあります。doブロックは、そのブロック内で最後に評価された値を返します。つまりは以下の様な感じです。

use strict;
use warnings;

my ($x, $y) = (1, 2);

my $sum = do {
    $x + $y
};

print $sum;    # => 3

1;


 doブロックの存在を知った私は思いました。「なるほど。こいつは無名即時関数っぽく使えるな」と。

 実際無名即時関数として書けば以下のようになります。

my $sum = sub {
    $x + $y
}->();


 これならdoブロックのほうが読みやすいな。

 よし、ではさらに読みやすくしちゃおうか。

my $sum = do {
    return $x + $y;
};


 こうしちゃいました。

 これダメです。


 returnはサブルーチンを即座に終了させ、呼び出し元に値を返します。doブロックはブロックであり、即時関数っぽいだけでサブルーチンではないので、doブロックの呼び出しに対して値を返す意図でreturnをすると、意図しない挙動になってしまいます。


 なので、doブロックを使用する場合はreturnの使い方に気をつけましょう、という話です。(こういうこともあるので、使用を出来るだけ控えるのが正解かもです)


終わりに

 以上、僕が最近やっちゃったミス集でした。

 間違っている箇所等ありましたら、教えていただけるとありがたいです。。


明日は

16日目の Perl Advent Calendar 2014magnolia_k_ さんです!