本連載では第一線のPerlハッカーが回替わりで執筆していきます。今回のハッカーは小林謙太さんで、
わかりやすく変更しやすいPerlコードを学ぶ
最初に、
ISUCONは処理速度を競うので、
ISUCONに限らず、
本稿では、
それらの解説に入る前に、
わかりやすさとは意味を理解するまでの認知の円滑さ
まず
本稿では認知心理学の言葉を借りて、
以降では、
意識せずに注目を集めるとわかりやすい
まず情報の知覚に関して、
視覚には
視覚のこの2つの処理の良いところをとって
意識せずに注目を集める方法の一つは、
記憶容量を節約するとわかりやすい
次に、
突然ですが、
また、
これらにより、
意図や行動を連想できるとわかりやすい
最後は、
「さくら」
この特性によって、price *= 1.
のようにマジックナンバーを使ったコードが挙げられます。価格を1割増加していることは推測できますが、price *= 1.
だけでは情報不足です。意図や行動を連想できるコードのほうが認知負荷は少なくなります。
また、
変更しやすくするために、もとに戻しやすくする
コードの
少し突飛な質問ですが、
ISUCONの参考実装では、
Perlの4つの新機能
ISUCON11の参考実装ではPerl 5.try/
、isa
、postderef
、signatures
という4つのPerlの新機能を利用しています。これらは、
try/catch ──罠に悩まされる心配をなくす
ISUCON11でPerl 5.try/
を使うためと言っても過言ではありません。
従来のPerlの例外処理では、eval
と特殊変数の$@
を使っていました。
eval { die "error!!" };
if ($@) {
# catch error
}
ただ、eval
、$@
を直接使うことはお勧めしません。$@
がグローバル変数のため、eval
が意図せずクリアされる問題があるためです。local $@
で回避はできますが、
eval { die "error!!" };
# このevalで$@がリセットされる
eval { };
if ($@) {
# no error...
}
この問題を回避するデファクトスタンダードの方法は、Try::Tiny
を使うことです。ただ、try/
の中でのreturn
です。次のコードでは、catch
でreturn
しても、sample_
関数の戻り値はSuccess
になります。この挙動が直感と反する人はいるでしょう。なお、Try::Tiny
のバージョン0.
use Try::Tiny;
sub sample_try {
try { die }
catch {
# このreturnでsample_tryが終わってほしい
# しかし、終わらない
return "Fail"
};
return "Success"
}
sample_try(); # => Success
Perl 5.try/
がビルトインされ、
use experimental qw(try);
sub sample_try {
try { die }
catch ($e) {
return "Fail"
} # try/catch構文になったので、末尾のセミコロンが不要
return "Success"
}
sample_try(); # => Fail
補足として、experimental
プラグマは、use feature qw(try)
でtry/
を有効化しつつ、nowarnings 'experimental::try'
をして、experimental
のバージョン0.
isa ──継承をスッキリと調べる
オブジェクトの継承関係を調べるisa
オペレータは、
従来は、blessed($o) && $o->isa('Some::Class')
と書いていました。継承関係を調べるisa
メソッドは、$o
がクラス名を指す文字列であっても呼び出せるため、Scalar::Util::blessed
でオブジェクトかどうかを確認する必要がありました。
isa
オペレータを使うと、$o isa Some::Class
と書くだけでオブジェクトの継承関係を調べられます。目的以外のコードが省け、
postderef ──スライスを簡潔にする
postderef
は、my ($foo,$bar) = $hash->@{qw/
と書けます。これは、$hash
から配列@
)foo
とbar
のキーの値を取り出すコードで、
my $arr = [qw/a b c d e/];
my $hash = { foo => 1, bar => 2, baz => 3 };
# 従来の書き方
my @all = @$arr;
my ($b, $c, $d) = @$arr[1..3];
my %copy = %$hash;
my %part = %$hash{qw/foo/}; # ( foo => 1 )
my ($bar, $baz) = @$hash{qw/bar baz/};
# postderefを利用
my @all = $arr->@*;
my ($b, $c, $d) = $arr->@[1..3];
my %copy = $hash->%*;
my %part = $hash->%{qw/foo/}; # ( foo => 1 )
my ($bar, $baz) = $hash->@{qw/bar baz/};
筆者はスライスがpostderef
の要だと考えます。ISUCON11では、MYSQL_
と書きました。
これは、@{MYSQL_
と書きます。@{MYSQL_
の部分を括弧を付けずに@{MYSQL_
と書くと、MYSQL_
が@MYSQL_
と扱われないようMYSQL_
関数を呼び出すためです。constant
プラグマが作る定数が関数であることを知っていなければ、
それと比べ、postderef
を使ったスライスは、host
とuser
を取り出す意図が簡潔にわかります。
use constant MYSQL_CONFIG => {
host => '127.0.0.1',
user => 'isu',
};
# postderefを利用
my ($host, $user) = MYSQL_CONFIG->@{qw/host user/};
# 従来だと
# @{MYSQL_CONFIG()}{qw/host user/} と書く必要がある
# @{MYSQL_CONFIG}{qw/host user/} と書くとエラーになる
signatures ──記号ばかりの引数をやめる
従来のPerlの関数の引数は、@_
に保持され、sub add { my ($a, $b) = @_; $a + $b }
のように@_
を分解して引数を扱います。配列@_
の最初の要素を取得するには、my $a = $_[0]
やmy $a = shift
とも書けます。これを理解するには、@_
の$i
番目の要素を$_[$i]
と書くことや、shift
関数の暗黙の引数に@_
が入るといった知識が必要です。これは初学者には優しくなく、
Perl 5.signatures
で、sub add($a, $b) { $a + $b }
と簡潔に書けます。従来の方法とsignatures
を使った方法を表1で比較しました。signatures
のほうが簡潔に見えると思います。
@_
とsignaturesの比較説明 | @_ を利用 | signaturesを利用 |
---|---|---|
2つの引数 | sub f { my ($a, $b) = @_; } | sub f($a,$b) { } |
デフォルト値 | sub f { my ($opt) = @_; $opt //= {}; } | sub f($opt={}) { } |
メソッド | sub m { my ($self, $key) = @_; } | sub m($self,$key) { } |
蛇足ですが、sub add($a, $b) { $a + $b }
は数値の足し算を期待していても、
sub add($a, $b) {
Int->check($a) or Carp::croak(Int->get_message($a));
Int->check($b) or Carp::croak(Int->get_message($b));
return $a + $b;
}
こういった値に制限を加える話は、
<続きの
本誌最新号をチェック!
WEB+DB PRESS Vol.130
2022年8月24日発売
B5判/
定価1,628円
ISBN978-4-297-13000-8
- 特集1
イミュータブルデータモデルで始める
実践データモデリング
業務の複雑さをシンプルに表現! - 特集2
いまはじめるFlutter
iOS/Android両対応アプリを開発してみよう - 特集3
作って学ぶWeb3
ブロックチェーン、スマートコントラクト、 NFT