PerlIO::via で遊ぶ

tomi-ru
2010-12-23

こんにちは、こんなにトミールを名乗っているのにいつまでたってもトミールの検索結果が抗ウイルス製剤のトミールです。今年も1位奪還は失敗しました。

さて、今日は Acme じゃないんだけど限りなく Acme ぽい PerlIO::via をご紹介しましょう。


役に立つ PerlIO 基礎知識(Acme に入る前に)

perl の IO レイヤーは、実はいろいろできて便利です。一番便利なのは、:encoding() でしょう。

こういう、

use autodie;
use Encode;
open(my $fh, '<', 'file.txt');
while (my $line = Encode::decode('cp932', <$fh>)) {
    print Encode::encode('utf-8', $line);
}

入力を cp932 エンコーディングで decode()、出力は utf-8 で encode() して出す、みたいな decode/encode をレイヤーにさせることができます。

use autodie;

# STDOUTから出す時に、自動でencode('utf-8')
binmode STDOUT => ':encoding(utf-8)';

# $fhから読むときは自動でdecode('cp932')
open(my $fh, '<:encoding(cp932)', 'file.txt');

while (my $line = <$fh>) {
    print $line;
}

なお、:encoding(utf-8) と :utf8 という指定方法はやりたいことは同じですが、前者はちゃんと utf-8 として正しいかのチェックが入り、後者は入りません。

そのため、出力にかませるなら :utf8 でもよいですが、入力にかませるなら(データがちゃんと utf-8 として valid か不明なので) :encoding(utf-8) の方が安全です。よくわかんないよという場合は、単に :encoding(エンコーディング名) という書き方だけ覚えておけばよいです。

http://search.cpan.org/perldoc?PerlIO::encoding


use open;(これも Acme ではない)

":encoding()" IOレイヤーの指定は binmode や open の第二引数にくっつける形じゃなく open モジュールで指定することもできます。(open モジュールは:encoding() 指定用モジュールと思っておけば良いと思います)

use utf8;
use open qw/:encoding(utf-8) :std/;

print "メリークリスマス! みなさま、どうぞ良い年をお迎えください。";

上の use open の仕方は、「そのスコープでのデフォルトIOレイヤーが :encoding(utf-8) に指定される」ことに加え、以下のコードを書いたのと同じです。

binmode STDIN  => ':encoding(utf-8)';
binmode STDOUT => ':encoding(utf-8)';
binmode STDERR => ':encoding(utf-8)';

なので、(use utf8;したスコープにある)“文字列”をprintしてもWide character in print at的警告はでません。


PerlIO::via(さあ、こっから Acme な話だ!)

そんな PerlIO レイヤー、勝手レイヤーを定義できるようになっています。

http://search.cpan.org/perldoc?PerlIO::via

作り方はカンタン。PerlIO::via::FooBarというPerlIO::via以下の名前空間でモジュールを作ると、:via(FooBar) というIOレイヤーが使えます。(ちなみに、openモジュールは :via の指定には対応していない)

モジュールは、tie みたいに、特定のメソッドを定義しておくと、IOレイヤーとして働かせることができます。


しかし、IOレイヤーで動いてくれてうれしいことってあんまりありません。PerlIO via で CPAN を検索すると、ネタとしか思えないもの(失礼)がざくざく出てきます。

全部インストールしてためしてないんだけど、

PerlIO::via::Base64は、どうやらIOレイヤーでBase64がかかるものらしい。う、うれしい・・こともあるカモ?

PerlIO::via::Babelfish これはどうやら IOレイヤーで翻訳をするらしい。日本語を print したつもりが英語になって出てしまう! 今日からバイリンガルうわーい!とかできる。みたいです。

use PerlIO::via::Babelfish(
    source => 'Japanese',
    target => 'English',
);

binmode STDOUT => ":via(Babelfish)";
print "メリークリスマス! みなさま、どうぞ良い年をお迎えください。";


つくってみよう

※【テロップ】当記事は PerlIO::via モジュールの制作を勧めるものではありません。


どうやら、PUSHED() が最初に呼ばれ、出力時には WRITE() が呼ばれるらしい。

ふむ。

さっそく、何をprintしてもイカ娘になるのをやってみましょう。

これ、我ながらひどいのは、自動で STDOUT に自分を設定しているところです。さすがは侵略者!

use PerlIO::via::Ikamusume;

use utf8;
use open qw/:encoding(utf-8) :std/;
print "メリークリスマス! みなさま、どうぞ良い年をお迎えください。";

useするだけで何と!

メリークリスマスでゲソ! みなさま、どうぞ良い年をお迎えくださいでゲソ。

と出力されます。

もはや普通にprintできません。


ついでに、サムライ語をつくったことも思い出したのでそれも作ってみましょう。

https://gist.github.com/753976

use PerlIO::via::Samurai;

use utf8;
use open qw/:encoding(utf-8) :std/;
print "メリークリスマス! みなさま、どうぞ良い年をお迎えください。";

これは、

メリークリスマスでござる! みなさま、なにとぞ良い年をお迎えくだされ。

と出ます。渋いですね。


PerlIOレイヤーはレイヤーというだけに多段にかけることもできます。

・・・ということは

・・・!

use PerlIO::via::Lou;
use PerlIO::via::Samurai;
use PerlIO::via::Ikamusume;

use utf8;
use open qw/:encoding(utf-8) :std/;

print <<'m(__)m';
メリークリスマス!

みなさま、どうぞ良い年をお迎えください。
読んでいただきありがとうございました。

来年もよろしくお願いします!
m(__)m

こんな感じになります。


メリークリスマスにてゲソ!

みなさま、なにとぞグッドなイヤーをお迎えくだされにてゲソ。
読んでいただきかたじけないにてゲソ。

ネクストイヤーもよろしくプリーズするでゲソ!