猫型です。Niigata.llの開催おめでとうございます。行けなくてくやしいです。資料だけここに公開します。タイトルは「Perlなめんな」です。dis られがちなオブジェクトシステムと例外機構の貧弱さについて現代的な Perl の視点から回答します。
「素のPerlのオブジェクトシステムは貧弱すぎる」。その通りです、でも Mouse とかを使えば結構素直に書けるのです。
Mooseっていうライブラリが一時期すごい流行りました。Mooseというのは Perl5 に現代的なオブジェクトシステムを導入するやつです。でも多機能すぎて重かったりしました。そこでほぼ Moose 互換なんだけど、内部で C 使って爆速にしたよっていう実装が出てきて、それが Mouseです。さらに最近は Moo ってのが出てきて、これも似たような使い方ができます。そういう Moose 族みたいなの使えばオブジェクト指向がキレイにかけます。
package Player { # 最近のPerlはこういうpakcage宣言の仕方ができる。クラスっぽく書ける
use Mouse; # Mouseっていうライブラリを使って「現代的な」書き方をする
# アクセサの定義
has weapon => (
is => "rw", # getter setterどっちも作るよ
isa => "Weapon", # 型だって指定できる
default => sub { Wepon::Null->new }, # デフォルト値も指定できる
lazy => 1, # デフォルト値の遅延ロードだってこの通り
);
has name => (
is => "ro", #read only, getterだけ作る
isa => "Str",
required => 1, # コンストラクタで設定されなかったときエラーにもできる
);
has hp => (
is => "ro",
isa => "Int",
required => 1,
);
# アクセサを作るとそれに応じたコンストラクターが勝手に定義される
# 自分で定義したいときは sub BUILD {} でコンストラクタを定義できる
sub attacked_by {
# 引数を受け取る
my ($self, $attacker) = @_;
my $damage = $attacker->weapon->strength;
$self->{hp} -= $damage;
print "@{[$attacker->name]} が @{[$attacker->wepon->name]} で攻撃!¥n";
print "@{[$self->name]} は @{[$damage]} のダメージを受けた!\n";
$self->die unless $self->is_alive;
}
sub is_alive {
my ($self) = @_;
$self->{hp} > 0
}
sub die {
my ($self) = @_;
print "「ぬわー」 @{[$self->name]}は死んでしまった\n";
}
__PACKAGE__->meta->make_immutable(); # おまじない
}
package Weapon {
}
package Weapon::Null {
use Mouse;
use parent -norequire => "Weapon";
has name => (is => "ro", default => "素手");
has strength => (is => "ro", default => 1);
__PACKAGE__->meta->make_immutable(); # おまじない
}
package Weapon::HinokiStick {
use Mouse;
use parent -norequire => "Weapon";
has name => (is => "ro", default => "ひのきの棒");
has strength => (is => "ro", default => 3);
__PACKAGE__->meta->make_immutable(); # おまじない
}
package main;
my $nekogata = Player->new(name => "猫型こたつむり", hp => 10);
my $jewelve = Player->new(name => "ジュエル武士", hp => 20);
# ジュエル武士がひのきの棒を装備
$jewelve->weapon(Wepon::HinokiStick->new);
# ねこがたがジュエル武士を素手で攻撃
$jewelve->attacked_by($nekogata);
# ジュエル武士の反撃
while ($nekogata->is_alive) {
$nekogata->attacked_by($jewelve);
}
$ perl niigatall.pl
猫型こたつむり が 素手 で攻撃! ジュエル武士 は 1 のダメージを受けた!
ジュエル武士 が ひのきの棒 で攻撃! 猫型こたつむり は 3 のダメージを受けた!
ジュエル武士 が ひのきの棒 で攻撃! 猫型こたつむり は 3 のダメージを受けた!
ジュエル武士 が ひのきの棒 で攻撃! 猫型こたつむり は 3 のダメージを受けた!
ジュエル武士 が ひのきの棒 で攻撃! 猫型こたつむり は 3 のダメージを受けた!
「ぬわー」 猫型こたつむりは死んでしまった
結構「ふつうのプログラミング言語」っぽく書けるのがわかると思う。素の言語仕様はしょぼいけど最近の perl で最近のライブラリ使えば普通に強力なオブジェクトシステムが使えます。今回は簡単な機能しか使ってないけど trait みたいな仕組みとかもあって結構強力なことがいろいろできます。現代的なオブジェクトシステムに必要な機能は一通りできると言って良いと思う。そのぶん強力なことしようと思えば学習コストがけっこうあるんだけど、強力な機能を使いたければ学習コストが高くなるのはどんな言語だって一緒ですしね。
「素の言語が貧弱なら、ライブラリで書き方を拡張しちゃえばいいじゃない!」というわけですね。
素のPerlは例外機構が貧弱すぎます。
でも、そんなときには Try::Tiny や Error など CPAN 上の便利なライブラリで書き方を拡張しちゃいましょう!さいきんのオススメはyappoさんが書いた Try::Lite です。
こんな風にかけます。
# 例外クラスを定義
package Error {
use Mouse;
has message => (isa => "Str", is => "ro");
__PACKAGE__->meta->make_immutable(); # おまじない
}
package NyanError {
use Mouse;
use parent -norequire => "Error";
__PACKAGE__->meta->make_immutable(); # おまじない
}
package WanError {
use Mouse;
use parent -norequire => "Error";
__PACKAGE__->meta->make_immutable(); # おまじない
}
package main;
use Try::Lite;
try {
# tryをネストできる
try {
die NyanError->new(message => "にゃーーーーーん!!!");
} (
# catchする例外を指定できる
'NyanError' => sub {
# $@という変数に例外が入ってる
warn $@->message;
},
);
try {
die WanError->new(message => "ワン!!!!!!!")
} (
'NyanError' => sub {
# にゃんエラーは発生しないのでここには絶対に来ない
},
# WanErrorを補足してないので上のほうまで伝播する
);
}(
# wanError is_a Errorなので Errorでキャッチできる
'Error' => sub {
warn $@->message;
},
);
$ perl niigatatry.pl
にゃーーーーーん!!! at niigatatry.pl line 25.
ワン!!!!!!! at niigatatry.pl line 40.
こんな感じで普通の例外機構っぽく使えます。文法がちょっと気持ち悪いなーみたいなこと思う場合は(yappoさんのブログにも書いてありますが) TryCatch みたいなものもあります。ですがTryCatchはソースフィルターという黒魔術を使っていてちょっと「ウッ」てなるみたいな感じも無いではないですね。とにかく、Perlの貧弱な例外機構を補うためのモジュールがたくさん CPAN に登録されています。
素の Perl が他の現代的な言語に比べると貧弱だっていうことに対して異を唱えるひとは(Perl Mongerでも)そう多くないと思います。ただ、これはあくまでレガシーなPerlとの互換性を保つためです。レガシーを意識しなくていいところで Perl を書くときには、こうやって現代的な書き方で書けばけっこう「普通の言語」として使えるんじゃないでしょうか。というわけで、ふたつの観点からPerlなめんなよって話をしてまとめにかえさせてもらいます。
言語を使って言語の機能を拡張していくみたいなことができるのは、Perl 言語自体が非常に柔軟だからです。このへんのは Rails が Ruby の柔軟さを充分に利用していろんな拡張を行っているのと似てますね。素の Perl が組み込みで提供している機能は貧弱でも、言語そのものが(実は)メタプログラミングに向いているので、ライブラリの力と相まって普通に強力な言語として使えるのです。
Perl ハッカーたちは、ほんとうにすごいひとたちの集まりです。他の「現代的な言語」のよいところを学び、それをどんどんPerlの世界に取り込んでいきます。Moose や Plack なんかそのいい例ですね。Perl という言語が持っているのは、言語仕様だけではありません。こういうどん欲で優秀なハッカーたちが Perl コミュニティにはたくさんいます。わたしは、Perl コミュニティを含めて「Perl」であると感じます。こういうひとたちが、Perlを日々ブラッシュアップし続けていることを知ると、あなたはそう簡単に「Perlオワコン」なんて言えないでしょう。このあたりのコミュニティを含めて言語であるみたいな感覚は、Ruby にもあるように感じます。
しかし、偉いひとは言いました。There's More Than One Way To Do It. 「やりかたはひとつではない」のですから、べつに Perl にこだわる必要はありません。Ruby のほうが歓迎される場所なら Ruby で書けばいいでしょう。Python の豊富な科学系ライブラリが必要なら Python で書けばいいでしょう。Java が歓迎されるところでは Java で書けばいいでしょう。PHP が歓迎される場所なら PHP で書けば良いし、VB が歓迎される場所なら VB で書けばいいし、C# が歓迎される場所なら C# で書けばいいんです。(ここで言う場所っていうのは、「どのレイヤーか」とかも含めて言ってます)。ただ、「Perlの文化ってすごいんだよ、ぜんぜんオワコンじゃないし、今から学ぶ意味だって充分にあるよ」というのがなかなか別の言語のひとたちに伝わらなくて結構誤解されがちなので、PerlMongers の末席を汚すものとして、LLerが集まる Niigata.ll でちゃんと伝えておこうって感じの発表でした!
わたしが産休に入ってしまいコミュニティ活動がなかなかできていないのですが、育休明けたら Niigata.pm のイベントやるので、興味を持ってくれたならぜひ参加して、現代的で実用的な Perl に触れてくれるといいんじゃないかな!
wepon ・・・.
weapon です.