Perl Hackers Hub

第14回最新Perl使いこなし術―リファレンスの引き方、5.10以降の新機能(2)

最近のPerlの機能

リファレンスの引き方がわかったところで、最近のPerlに追加された機能を紹介します。Perlは枯れた言語というイメージがある人もいると思いますが、まだまだ活発に開発が続けられており、バージョンが上がるたびに便利な機能が増えています。

Perlの安定バージョンについて

perlの安定版はバージョン番号X.Y.ZのマイナーバージョンYが偶数のものです。最近の安定版は5.8(2002年7 月リリース⁠⁠、5.10(2007 年12 月リリース⁠⁠、5.12(2010 年4 月リリース⁠⁠、5.14(2011 年5 月リリース)です。本稿ではperl 5.10以降に実装された機能を「最近のPerlの機能」として紹介します。

perlの公式安定版として一番新しいのは5.14ですが、多くの人はまだ5.8系を使っているでしょう(非常に安定しているため⁠⁠。しかし5.8系の最新5.8.9のリリースは2008年12月と、よく言えば枯れているとはいえ、古いものです。現在筆者は個人的には5.14.2を使っており、仕事で使っているのも5.12以降のものです。Mac OS X Lionに標準で入っているのも5.12です。

以降では最近のPerlの機能を、どこがどう便利なのか、実際にどんな感じで使うのか、あるいは実際に使えるのかを解説します。

5.10からの機能

まずは5.10からの機能です。

featureプラグマ─⁠─新機能を使うおまじない

最初はあっと驚く機能ではないけれど重要な機能の紹介です。新しい機能は、古いperlとの互換性のためデフォルトでは無効になっているものもあります。featureプラグマはそういった機能を有効にしたいときに使います。

これはプログラム冒頭でuse feature qw(say switch)のように書く「おまじない」です。

たとえばuse feature ':5.10'と書けば5.10で使える機能はすべて有効になります。use 5.10.0;use v5.10;と書いても同じです。ただし、5.12以降でuse v5.12;とすると、use feature ':5.12'に加えてuse strict;相当にもなります。筆者はuse strict効果を狙いつつ、なんとなくカッコいいuse v5.12;を好んで使っています。

use feature 'say';とすると、次のsay関数が使えるようになります。

say関数─⁠─改行付きで出力

print関数は\nを自分で付けないと改行してくれませんが、say関数は改行を付けて出力してくれます。say $string;はprint $string, "\n";と同じです。ちょっと出力したいときに改行をいちいち書かなくてよいのは、ストレスがかなり軽減されます。

say関数自体はPerlの文法に何の影響も与えていないので、5.8以下でも同じように動くモジュールとしてCPANでPerl6::Sayが提供されており、これをuseすることでも同じ関数を得られます。

defined-or演算子─⁠─undefとそれ以外を区別

defined-or演算子はオペランドを2つ($A、$B)とり、$A // $Bのように書きます。名前のとおり、defined $Aが真でなければ$Bを評価して返します。似たような演算子としてor 演算子$A || $Bがありますが、defined-or演算子はこれのundefの場合のみ$Bが評価されるバージョンと考えることができます。

次のように使います。

my $bar = $foo // 1;

$fooがundefの場合、$barには1が代入されます。

また、自己代入演算子も定義されており、ほかの似たような演算子と同じく、次のようにも使えます。

$baz->{foo} //= 'hello, world!';

$baz->{foo}がundef ならば'hello, world!'を代入する、という意味です。

この演算子は非常に重要です。Perlにおいては空の文字列の''も、文字列の'0'も、数値の0も偽になるため、or演算子では期待するよりも多くの場合で後続のオペランドが評価される場合があります。典型的な例として次のようなコードがあります。

sub title {
  my ($self) = @_;
  $self->{_title} ||= $self->retrieve_title_from_db;
}

この例では、データベースからタイトル(文字列)を取得するtitleメソッドを定義しています。titleメソッド自体は何度も呼ぶ可能性があるので、データベースの結果を$selfにキャッシュしています。

defined-or演算子を使わずにor演算子を使っているため、$self->retrieve_title_from_dbが空文字列の場合、何度も何度も$self->retrieve_title_from_dbが評価されることになってしまいます。データベースアクセスが発生する場合は特に望ましくない挙動です。defined-or演算子を使えば、$self->{_title}がundefの場合のみ$self->retrieve_title_from_dbが評価されることになり安心です。

5.8以前でこの演算子と同じことをしようとすると、次のようになります。

my $bar = defined $foo ? $foo : 1;

$baz->{foo} = defined $baz->{foo} ? $baz->{foo} : 1;

sub title {
  my ($self) = @_;
  $self->{_title} =
    defined $self->{_title} ? $self->{_title} :
    $self->retrieve_title_from_db;
}

自己代入の場合などは特に冗長になって書きにくく、可読性が下がります。このようにdefined-or演算子がない場合、条件演算子を使わなければなりませんが、面倒なためor演算子を使いがちになり、意図しない処理が走り結果的にバグにつながります。

この演算子を使うためだけでも、5.10以降を使う理由になると思います。

正規表現のnamed-capture─⁠─キャプチャの参照が楽に

Perlの正規表現では、括弧()を使うことでその部分にマッチした文字列は$1、$2、$3………という特別な変数に代入され(キャプチャと言います⁠⁠、あとから取得できます。でも、括弧の数が増えてきたり、正規表現が複雑になってくると、どれがどの変数に入るのかわかりにくくなります。これを解決するのがnamedcaptureです。

次の例のようにただの括弧の代わりに(?という書き方をすることで、$+{識別子}$-{識別子}という書き方で参照できるようになります(%+%-というハッシュに値が入り、$+{識別子}でハッシュの値を取得するという意味です⁠⁠。

my $string = 'http://example.com/foo/bar';
$string =~ m{^http://(?<host>[^\s/]+)(?<path>/\S+)$};

say $+{host};
say $+{path};

特に便利なのは、パイプで区切って複数の似たような表現を羅列するときでしょう。次のようにした場合、マッチしたのがどちらであれ、$+{rgb}にマッチした文字列が入ります。

$string =~ m{
  (?<rgb>\#[0-9a-f]{6}) |
  (?<rgb>\#[0-9a-f]{3})
}xi;

say $+{rgb};

同じ名前で複数の場所がキャプチャされた場合、$+{rgb}には最後にマッチした内容が入ります。全部欲しいケースでは、次のように$-{識別子}という特殊な変数を使います。$-{識別子}には配列リファレンスですべての値が入っています。

my $string = "aaa bbb ccc";
$string =~ m{(?<chars>...) (?<chars>...)};

$-{chars}; #=> ['aaa', 'bbb']

正規表現が複雑になるほど、この機能は便利です。どれがキャプチャの括弧なのかを見極めながら括弧の数を数える生活から解放されます。

スマートマッチ演算子─⁠─いい感じに真偽を返す

スマートマッチ演算子は、$A ~~ $Bとして使います。スマート(賢い)という名前のとおり、⁠あらゆる値をいい感じにマッチさせて真か偽を返す」演算子です。

簡単な例を挙げます。

warn 'a' ~~ [qw/a b c/];
#=> 1
warn 'x' ~~ [qw/a b c/];
#=> Warning: something's wrong

スマートマッチ演算子は右辺が配列リファレンスだと、その中の要素1つにマッチすれば真を返します。便利ですね。

今のところこの演算子は速度が非常に遅いのと、普通に比較したりgrepしてもそんなに手間もかからず可読性も変化しないこともあり、あまり使われていません。筆者も普通は使わないようにしています。ただ、オーバーロードによってオブジェクトごとの比較を定義し、次に説明するswitch文を組み合わせることで、非常に簡潔なコードにできる可能性があり、楽しそうな演算子です。

挙動はオペランドの型によって変わります。全部のパターンについてはperldoc perlsynに書いてあります。

switch文─⁠─条件を並列に書く

switch文は、人によっては待ちに待った機能でしょう。switch文という名前が付いていますが、文法上はgiven/whenというキーワードを使います。複数の条件分岐を並べて書けます。

次の例のように、$foo"foo""bar"か、それ以外かdefaultによって別の処理を書けます。givenに与えられた値は暗黙的に$_へも代入されます。

given ($foo) {
  when ("foo") { ... }
  when ("bar") { ... }
  default { ... }
}

また、switch文の真偽判定はスマートマッチ演算子で行われます。つまり次のように、givenで与えられた値と、whenで与えられた値を~~で演算するのと同じです。

given ($foo) {
  $foo ~~ [0..9] と同じ
  when ([0..9]) { ... }
  $foo ~~ [10..19] と同じ
  when ([10..19]) { ... }
  default { ... }
}

しかし、次のようにwhen節に条件を指定した場合、givenに与えられた変数は無視されます。

given (0) {
  when (2 == 2) { say 1 } #=> 1
}

不思議な挙動に感じられるかもしれませんが、さまざまな条件を並列して書く場合は都合の良い仕様です。

switch文も細かい挙動は複雑ですが、これもperldoc perlsynに書いてありますので、よくわからない挙動をするときは参照するのが良いですね。

state関数─⁠─新しいスコープの導入

state関数は、state $i = 0;と、myやlocalのように変数を宣言するときに使います。これはスコープ的にはmyとまったく同じですが、myと違って変数が一度しか初期化されない特性があります。

例を見ればすぐわかります。次のコードのcounterサブルーチンでstate $i = 0;と宣言していますが、初期化で0が$iに代入されるのは最初だけで、2度目からは前回の値が引き継がれます。

sub counter {
  state $i = 0;
  $i++;
}

say counter(); #=> 0
say counter(); #=> 1
say counter(); #=> 2

state相当のことは5.8以前でも、myを使って次のようにも書くことができます。

{ my $i = 0;
  sub counter {
    $i++;
  }
}

state関数はこれを簡潔に書けるようにしたものです。

encoding::warnings─⁠─文字化けするコードを検出

Perl本体の機能ではありませんが、5.10から追加されたかなり便利なモジュールです。Perlで文字列とバイト列を区別せずにコードを書いていると、しばしば文字化けが発生します。これはUTF-8のバイト列と文字列を結合しようとしたときに、Perlが自動的にバイト列をLatin-1と見なして文字列にアップグレードしようとするためです(UTF-8だろうがなんだろうがLatin-1と見なすため⁠⁠。この挙動自体は歴史的経緯で、変更するのは互換性が失われ過ぎるため難しいようです。

encoding::warningsによって、この暗黙的に文字列にアップグレードされる挙動を警告するようにできます。これは文字化けが発生したとき、あるいは発生させそうなときにどこでそれが起こっているかがすぐわかるので、非常に便利です。

現代においてはこの暗黙的なアップグレードを狙ってコードを書くことはありえないと思いますので、use encoding::warnings 'FATAL'として、発生した時点で警告ではなくperlインタプリタを強制的に終了させるのが楽しいと思います。このエラーが発生するコードはおかしいので、ちゃんとEncodeモジュールでデコード/エンコードしたいところです。

おすすめ記事

記事・ニュース一覧