Translate

2013年8月18日日曜日

エラー情報は、発生行?呼出し行 warn/die or carp/croak

まずは、Debugモジュールを作成し、Printするメソッドを定義します。
ただし、Printする文字列がない時つまり引数が渡されなかったらエラーで終了としましょう!

use strict;                                                                              
use warnings;
use utf8;
use feature 'say';
binmode STDOUT, ":utf8";
binmode STDERR, ":utf8";

my $obj = Debug->new();
say $obj->Print("abc");
say $obj->Print("def");
say $obj->Print();
say $obj->Print("ghi");

package Debug;

sub new{                                                                                  
    return bless {}, shift;
}

sub Print{
    my ($self, $str) = @_;
    if (@_!=2){ die("引数を一つだけ指定してください") };

    return $str;
}

これを実行するとエラーで落ちます。
abc
def
引数を一つだけ指定してください at sample.pl line 22.

確かにエラーが発生した行の情報が表示されていて
これはこれで正しいのですが、あちこちから呼ばれる、データがトリガーとなって
エラーとなるケースでは呼び出しもとがどこなのか知りたいですよね。
つまり、呼び出しもとの行数を知りたい。
そんな時は、Carpモジュール。

use strict;                                                                              
use warnings;
use utf8;
use feature 'say';
binmode STDOUT, ":utf8";
binmode STDERR, ":utf8";

my $obj = Debug->new();
say $obj->Print("abc");
say $obj->Print("def");
say $obj->Print();
say $obj->Print("ghi");

package Debug;
use Carp;

sub new{                                                                                  
    return bless {}, shift;
}

sub Print{
    my ($self, $str) = @_;
    if (@_!=2){ croak("引数を一つだけ指定してください") };

    return $str;
}

実行結果:
abc
def
引数を一つだけ指定してください at sample.pl line 11.

引数を一つだけ指定してください。といわれ11行を確認すると
say $obj->Print();
ならば修正するのも楽ですね。

warnの代わりはcarpで
dieの代わりはcroakとなっています。

ロジックに問題の可能性があればwarnやdieを使い
呼び出し元がトリガーとなる場合carpやcroakを適宜使い分ければよいと思います。

2013年8月17日土曜日

マルチバイと文字列の文字列操作のお話

"abc"という文字列が何文字か調べてみましょう。

use strict;
use warnings;
use feature 'say';

my $str = "abc";
say $str. "は、". length($str). "文字です";

実行結果:
abcは、3文字です

問題ありませんね。
では、"あいうえお"が何文字か調べてみましょう。
use strict;
use warnings;
use feature 'say';

my $str = "abc";
say $str. "は、". length($str). "文字です";

実行結果:
あいうえおは、15文字です

はい、15文字ですn…
えぇぇぇぇぇ。それ困ります。

このようにマルチバイト文字列の場合見た目の文字数通りカウントできないため
文字列操作で文字数を絡める場合問題となります。
そのため、見た目の文字数と一致させるためには、
プログラム内部で使用する"内部文字列"という表現に変換してあげる必要があります。
use strict;
use warnings;
use feature 'say';
use utf8;

my $str = "あいうえお";
say $str. "は、". length($str). "文字です";

簡単です。use utf8;とつけただけです。

実行結果:
Wide character in say at t.pl line 7.
あいうえおは、5文字です

5文字と表示されているので正しくカウントされました。
ただ、Wide character in say 〜 としかられました。
これは、何かというと、プログラム内部で扱う文字表現のまま出力するとおこられるのです。
perlの場合互換性を大事にされているので昔のプログラムもそのまま動くように配慮されているため
バージョンがあがってから追加された機能はいろいろ一手間加えてあげないといけないわけです。

そんなわけで一手間加えてみます。
use strict;
use warnings;
use feature 'say';
use utf8;
binmode STDOUT, ":utf8";

my $str = "あいうえお";
say $str. "は、". length($str). "文字です";
binmode STDOUT, ":utf8";
を追加しました。
ターミナルへの出力(いわゆる標準出力)の際に問題となっているので
binmode STDOUTで標準出力を行うもの全てに対し処理が加えられます。
そして、ターミナルはutf8の文字列を受け付けているので:utf8として出力しています。
単純ですね。

内部文字列表現かそうじゃないかは、ぱっとみわからないので判断しづらいかもしれませんが
ともかく、perlに文字列を渡す時に内部文字列に変換して外に出す時に元に戻すということをすればよいわけです。

ここまでのまとめ


次。
エラー処理をしたい時warnやdieを使うと思いますが
use strict;
use warnings;
use feature 'say';
use utf8;
binmode STDOUT, ":utf8";

warn("警告");
die("すっごい致命的なんです");  

実行結果:
Wide character in warn at t.pl line 7.
警告 at t.pl line 7.
Wide character in die at t.pl line 8.
すっごい致命的なんです at t.pl line 8.

出ました!!
エラー系は標準出力と異なるため、STDOUTでは太刀打ちできません。

use strict;
use warnings;
use feature 'say';
use utf8;
binmode STDOUT, ":utf8";
binmode STDERR, ":utf8";

warn("警告");
die("すっごい致命的なんです");
binmode STDERR, ":utf8";
標準エラーを追加しました。

実行結果:
警告 at t.pl line 8.
すっごい致命的なんです at t.pl line 9.

いい感じですね。
まとめです。


さて、他に考えられるI/Oは…
標準入力を考えてみましょう
se strict;
use warnings;
use feature 'say';
use utf8;
binmode STDOUT, ":utf8";
binmode STDERR, ":utf8";

chomp(my $input = );
say $input;

"あいうえお"って入力すると・・・

実行結果:
あいうえお
あã

文字化けを起こしました。
何が起きているか確認してみましょう。

現時点で、外からやってきた文字列は内部の文字列表現になっていません。
それにもかかわらず、binmode STDOUT, ":utf8";があるため
文字列の出力の際に、内部表現だという前提で変換しているため、
おかしなことになったわけです。

もうすでに対策は薄々気づけそうですが

use strict;
use warnings;
use feature 'say';
use utf8;
binmode STDIN,  ":utf8";
binmode STDOUT, ":utf8";
binmode STDERR, ":utf8";

chomp(my $input = );
say $input;
binmode STDIN, ":utf8";
を追加しました

実行結果:
あいうえお
あいうえお

理由がわかれば、簡単ですね。


今度はファイルからのよみこみ書き込みを行ってみます。
まずは読み込み
use strict;
use warnings;
use feature 'say';
use utf8;
binmode STDIN,  ":utf8";
binmode STDOUT, ":utf8";
binmode STDERR, ":utf8";

open my $fh, '<', 'string.txt' or die "$!";
say <$fh>;
close $fh;

尚、string.txt には あいうえお と記載されているだけです。
1行取得して出力した結果は以下の通りです。

実行結果:
あã


また文字化けです。
当然ながら、perlに渡す前に内部表現に変換していないからです。
対応してみます。

use strict;
use warnings;
use feature 'say';
use utf8;
binmode STDIN,  ":utf8";
binmode STDOUT, ":utf8";
binmode STDERR, ":utf8";

open my $fh, '<:encoding(utf8)', 'string.txt' or die "$!";
say <$fh>;
close $fh;

'<' から '<:encoding(utf8)' へ変更しています。 string.txt はutf8で保存されています。 実行結果:

あいうえお

うまくいきました。
ファイルで書き込みを行う時も同じ操作になります。
'>:encoding(utf8)' と言った感じです。




残すは、他のソースコードとのやり取りですね。
つまり、他のモジュールへ文字列を渡したり受け取ったりする時です。
これは、モジュールごとに内部表現で渡すべきか、そうじゃないかは対応がまちまちなので
実際に試してみない限りなんとも言えません。
ケースバイケースで対応しましょ。
内部表現にしたい時は、use Encode; を使い decode_utf8('文字列')
内部表現からもとに戻す時は、use Enocde;を使いencode_utf8('文字列')
とすればよいです。

なお、encode_utf8 はenocde('utf8', '文字列') 別の書き方です。
違うエンコードの時は、encode('文字コード', '文字列')
decode('文字コード', '文字列')としてください。

最後のまとめです。
INとOUTで分けておきました。
これさえ注意すればなんとかなりますね。


基本的な考えとして、use utf8は必ずつける。
外から受け取る時は内部表現に変更する
外へ出す時は、内部表現をやめる
モジュール間でやりとりするときは、そのまま(内部表現)でやりとりする
という感じでよいのではないでしょうか?

2013年8月16日金曜日

リファレンス+do

ただ読み込むだけのもっとも簡単な設定ファイルの仕組みが欲しいのであれば
ファイルに直接リファレンスを書き込みそれをdoしてperlとして解釈するのが一番簡単。

まずは設定ファイルを作成する
ファイル名:config
database = 'fizz'
user     = 'bazz'
pass     = 'fizzbazz'

ファイル名:Sample.pl
use strict;
use warnings;
use utf8;  
use feature 'say';
binmode STDOUT, ":utf8";
use Dumpvalue; my $d = Dumpvalue->new();

my $configFile = 'config';
my $config = do $configFile or die "$!";

say $config->{database};
say $config->{user};
say $config->{pass};

$d->dumpValue($config);

実行結果:
fizz
bazz
fizzbazz
'database' => 'fizz'
'pass' => 'fizzbazz'
'user' => 'bazz'

個人でさくっと読み込みのみの気軽な物が・・・
となるとこれが一番ですかね。

Config::Simple

■簡単な設定ファイルを操作するモジュール

まずは設定ファイルを作成する
ファイル名:config
database = 'fizz'
user     = 'bazz'
pass     = 'fizzbazz'

ファイル名:Sample.pl
use strict;
use warnings;
use utf8;
use feature 'say';
use Config::Simple;
binmode STDOUT, ":utf8";

my $config = Config::Simple->new('./config');
say $config->param('database');
say $config->param('user');
say $config->param('pass'); 

実行結果:
fizz
bazz
fizzbazz

new する時に、ファイルパスを指定し、その後paramメソッドで値を取得することができるようです


ちょっとした物を記録しておき使うのであればこんな感じでよさそうですね。
ただ、同一のキー名を使用したい場合はセクションで区切ることで使い分けができます。
ファイル名:config
[001]
database = 'db001'
user     = 'user001'
pass     = 'pass001'

[002]
database = 'db002'
user     = 'user002'
pass     = 'pass002'

use strict;
use warnings;
use utf8;
use feature 'say';
use Config::Simple;
binmode STDOUT, ":utf8";

my $config = Config::Simple->new('./config');

say $config->param("001.database");
say $config->param("002.database");

実行結果:
db001
db002

今度は、paramメソッドで[セクション名].[キー名]を指定することで値にアクセスできます。

ちなみに、ハッシュ化することも可能で
オブジェクト->vars(); とすればできます。
use strict;
use warnings;
use utf8;  
use feature 'say';
use Config::Simple;
binmode STDOUT, ":utf8";
use Dumpvalue; my $d = Dumpvalue->new();

my $config = Config::Simple->new('./config01');
my $hash   = $config->vars();
$d->dumpValue($hash);

say $hash->{'001.user'};                                                           
say $hash->{'002.user'};

実行結果:
'001.database' => 'db001'
'001.pass' => 'pass001'
'001.user' => 'user001'
'002.database' => 'db002'
'002.pass' => 'pass002'
'002.user' => 'user002'
user001
user002


param関数とかじゃなくて、一発でハッシュに放り込んでくれると便利そうですね。
ということで他のConfig系のモジュールも探してみよう。

2013年8月14日水曜日

perlの変更点が知りたい時

perldoc.jpに翻訳されたものがあるのでそちらを見るとよいと思います。

自分もあれって何のバージョンから変更されたっけ??
となるので、たまに参照します。
ということで、おもだったとこのリンクでもはっておきます。

perl v5.18.0 での変更点
→5.16.0 リリースと 5.18.0 リリースの変更点

perl v5.16.0 での変更点
→5.14.0 リリースと 5.16.0 リリースの変更点

perl v5.14.0 での変更点
→5.12.0 リリースと 5.14.0 リリースの変更点

perl v5.12.0 での変更点
→5.10.0 リリースと 5.12.0 リリースの変更点

perl 5.10.0 での変更点
→5.8.8 リリースと 5.10.0 リリースとでの相異点


ラクダ本や、リャマ本を読んでようやく、スタートラインにたち、
ネットから最新情報を取得してがんばろう!
とした矢先に あれ?この記述なんだ??
みたいなことはよくあるものですよね。
おまけに、ググラビリティが低くて見つかりにくいのです…
そんなわけで、気になったものを簡単に羅列してみます。

■+{} 波括弧の前のプラス
波括弧には、
関数を囲うためとハッシュのリファレンスの2通り使われるため
perlにこれはハッシュのリファレンスのための波括弧なんですよ。
ってことを知らせるために、+{}としている

■use 5.010; or use feature ':5.10'; or use feature 'say'

print "値¥n";
とした場合、いちいち改行文字なんぞくっつけていたと思いますが
say "値";
say関数であれば、改行文字を自動的にくっつけてくれる便利さんなんです。
という話をどこかでみかけたと思います。
そして、say関数を使うにはどうすればよいの?
とあちこち眺めてみると上記の記述にでくわすわけです。

use feature 'say';
は、5.10で追加になったsayという関数のみ使えるようにする記述の仕方

use feature ':5.10';
は、5.10で追加になった機能すべて使えるようにする記述

use 5.0.10;
は、5.10で追加になった機能すべて使えるようにして
かつそれ未満のバージョンのperlで実行しようとしたらすぐにエラーとなり停止させれます。

つまり、明示的にsayって関数だけがつかいたいだけなんですよ!!
他、あるかしらんしー
sayだけあれば幸せだしー
という場合は、use feature 'say';
とかけば、他の人が読んでも 他のものはつかっていないことがはっきりします。

ただ、5.12以降を指定すると use strictしたのと同等になり
いちいちuse strict; と書かなくてすむので
use 5.0.12; のように記述するのを好む人もいると思います。

という感じですかね。