ようこそゲストさん

Kerosoft : Modus Operandi

2009/02/26(Thu) 変数内での関数の展開

はてブ情報 はてブに登録 はてブ数 2009/02/26 23:43 Languages::Perl
変数の展開の展開 +αでは、埋め込んだ関数を展開して評価する方法を書いたが、あんな面倒な事をやらなくてもPerl本来の機能で簡単にできたらしい。

ダブルクオート(qq)内から関数を呼び出す

簡単な例を下に示す。
#!/usr/local/bin/perl

print qq|5*2は@{[a(5)]です}\n|;

sub a{
    return $_[0]*2;
}
答えは10と表示される。同様に、ヒアドキュメントでも有効だ。

ヒアドキュメントから関数を呼ぶ

#!/usr/local/bin/perl

print << "_EOM_";
5*2は@{[a(5)]}です。
_EOM_

sub a{
    return $_[0]*2;
}

いやぁ、今までPHPみたくヒアドキュメント風に書きつつ関数を展開することができないと思いこんでいたため、
  • ソースの上の方で関数の処理を束ね、それを一時変数に入れて、下の方のヒアドキュメントで大量に参照する
という方式か、
  • ヒアドキュメントではなく、q||やqq||を大量に使って文字列連結し、途中に関数呼び出しを挟む
という非効率極まりない書き方をしていた。あぁ、もったいな……。

@{[関数]}という構文は少々打ちづらいけど、とても便利なので、今後多用しようーっと!

ヒアドキュメントに匿名メソッドを埋め込む

欲を言えば、単発の関数だけじゃなくて匿名メソッド(無名メソッド)が呼べたら便利じゃん?*1
ということで、いろいろ試してみたらうまくいった。これすげー!
#!/usr/bin/perl

print << "_EOM_";
呼ばれたのは…
@{
    if(time%2){
        [&A];
    }else{
        [&B];
    }
}
_EOM_

sub A{
    return qq|A!|;
}

sub B{
    return qq|B!|;
}
結果はいわずもがな、
$ ./test.cgi
呼ばれたのは…
A!
$ ./test.cgi
呼ばれたのは…
B!
のようにtime%2の演算結果に従って変わります。
ちなみに、[&a];という部分をprint &a;などと書くと、ヒアドキュメントより先にそちらが実行されて
$ ./test.cgi
A!呼ばれたのは…
のようになるので、注意。


いやぁ、Perl万能だなぁ。

*1 : C#でdelegateによる匿名メソッドに毒されてまして……^^;

参考になった記事や興味深かった記事は、他の人も見つけやすいようにリンクはてブしていただけると助かります…。 コメントも歓迎です。

1: Sota 2009年02月28日(Sat) 午前0時59分

ダブルクォートやヒアで関数呼べるのは知らなかったです。
ただ、関数がundef返したりエラーったときのハンドリングが難しくなるし、
関数部分で複雑なことやりだすと、Viewの部分がゴチャゴチャになってスパゲティになりそうです。
また、CGIなど長いテキストの出力のなかでロジックが必要になるなら、HTML::TemplateやTemplate-Toolkitがオススメです

2: Keroberos 2009年03月02日(Mon) 午前4時21分

コメントありがとうございます。
そうですね、複雑なことになってくるとその辺のテンプレ支援PerlModuleを呼んだ方がスッキリするでしょうね。(Template-Toolkitに関してはBlog、読ませていただきました。)
ただTemplate独特の記法というのが、それを知らない他の人に見せた時の可読性を下げそうなので、Perlが分かってれば中でやってることが分かりやすいように↑の記事のような手法を使うのもアリかな、とは思います。
今現在かかわっているプロジェクトではDBD::mysqlを使ってアレコレやってるんですが、SELECT一つとってもなかなかスマートと思える書き方ができない(fetchall_arrayrefして$_->[0]とか…)んですけど、なんかいい方法ありますかねぇ。

3: Sota 2009年03月05日(Thu) 午前0時14分

確かにテンプレートエンジンは学習コストかかりますがそこまで高くないと思います。また、今回のような手法はあまり使われていないことを考えると、むしろ可読性に関しては下がると思います。
ただ、ログの表示などちょっと内容を見たい、かつ、今後メンテナンスを必要としない場合はアリだと思います。
DBIに関しては、fetchrow_hashrefを使うと読みやすいかとは思います。
my $dbh = DBI->connect('dbd:mysql:db:host', 'user, 'pass');
my $sth = $dbh->prepare(q{SELECT id, name FROM member});
$sth->execute();
while( my $row = $sth->fetchrow_hashref() ) {
print "$row->{id}, $row->{name}\n";
}
http://www.rfs.jp/sb/perl/ の6章が参考になると思います。
bind_columnsを使ってうまくモジュールで隠蔽できればわりとスマートになります。
あとはDBIx::ClassなどのORマッパーを使うぐらいですかね・・・。
ただ、ORマッパーはちょっと慣れていないので・・・

4: Keroberos 2009年03月06日(Fri) 午前3時55分

リプライありがとうございます。

SmartさんのHPはまず最初に参考にしましたが、この一文
>fetchrow_hashrefはfetchやfetchrow_arrayほど効率よくありません。
が気になって(別に速度面で神経質にならざるを得ない状況ではないですが)選択肢から外していました。
また"->"をいっぱい書くのが面倒なので、リストで受けられるselectrow_arrayが便利かと思いましたが、rowが0行の時にundefになるのか空になるのか~?とか仮変数を用意する手間があったり、何かしら手間がかかってやだなぁというとこです。

しかしこれといっていい案も浮かばないですし、"こうやって書けるだけでも楽をしている"と思って使うしかないんでしょうかね…。

5: Sota 2009年03月07日(Sat) 午前1時51分

> また"->"をいっぱい書くのが面倒なので
あとはこんなやり方ぐらいですかね。
--
#!/usr/bin/perl
use strict;
use warnings;

use DBI;

my $dbh = DBI->connect('dbi:mysql:member:HOST', 'USER', 'PASS');
my $sth = $dbh->prepare('SELECT id, name, age FROM member');

$sth->execute();

my %row;
$sth->bind_columns( \(@row{ @{$sth->{NAME_lc}} }));

while ($sth->fetch) {
print "$row{id}, $row{name}, $row{age}\n";
}
--
参考:http://pc5.2ch.net/php/kako/1063/10635/1063562491.html

かなり癖が強いですが。。。
NAME_lcでカラム名の配列を持ってきて、
それをハッシュの内側の値にbind_columnsで結びつける。

行がこれ以上見つからない、または0行の場合、fetchでundefを返してwhileを抜けます。
fetchなのでfetchrow_hashrefより速いはず。

6: Keroberos 2009年03月07日(Sat) 午前2時25分

Sotaさん、長々とお付き合いくださり感謝いたします。

おぉこれは凄いですね。
やや強引な感じはしますが、パッと見、かなり満足です。
引数の番号をずらずら書くのは書くのが面倒という他に、後々にSQL文を修正したときが怖いなと思っていたのもあったのですが、ハッシュに突っ込むことで、そのリスクを軽減できていますね。

便利そうなので、早速使ってみようと思います。


名前:  非公開コメント   

  • TB-URL  http://mo.kerosoft.com/0106/tb/