Type as State, Coercion as Hook

MooseのTypeConstraintは,型というよりはあるデータの性質を表現したものだと考えられる。また,TypeCoercionは,ある型(=あるデータの性質)に対してフックを掛けるメカニズムと考えられる。
このように考えると,Coercionを利用していろいろと面白いことができるのではないかと思う。

たとえば,以下のようにCoercionを利用してエンコーディングを推測することができる*1

#!perl
package E;
use Any::Moose;
use Any::Moose '::Util::TypeConstraints';
use Encode qw(encode decode FB_QUIET);

subtype 'UniStr', as 'Str', where { utf8::is_utf8($_) };

# define types represent an encoding
subtype 'utf8', as 'Str',
    where { decode('utf8',      $_, FB_QUIET) ne '' };
subtype 'shift_jis', as 'Str',
    where { decode('shift_jis', $_, FB_QUIET) ne '' };
subtype 'euc_jp', as 'Str',
    where { decode('euc-jp',    $_, FB_QUIET) ne '' };

coerce 'UniStr',
    from 'utf8',      via { decode('utf8',      $_) },
    from 'euc_jp',    via { decode('euc-jp',    $_) },
    from 'shift_jis', via { decode('shift_jis', $_) },
;

# testing

# "ko-n-ni-chi-wa-!" in Hiragana
my $us = "\x{3053}\x{3093}\x{306b}\x{3061}\x{306f}\x{ff01}\n";

my $utf8  = encode('utf8',   $us);
my $sjis  = encode('shift_jis', $us);
my $eucjp = encode('euc-jp', $us);

my $UniStr = find_type_constraint('UniStr');

binmode STDOUT, 'utf8';

print $UniStr->coerce($us);
print $UniStr->coerce($utf8);
print $UniStr->coerce($sjis);
print $UniStr->coerce($eucjp);
__END__

utf8-flagged文字列はもちろん,様々なエンコーディングのバイト列を渡しても,四回正しく「こんにちは」と出力される*2

UniStrとそれに対するcoerce()の定義は,「coerce()に渡された文字列がutf8-flaggedでなければそれぞれのエンコーディングでデコードを試し,成功すればそのエンコーディングと見なせ」という意味である。そして成功したエンコーディングがあれば,対応するvia{ ... }が実行され,実際のデコードが行われる*3。ここでは,"shift_jis"などのsubtypeは純粋にcoercionのためだけに定義している。

また,最近のMooseにはmatch_on_typeというユーティリティ関数がある。これでもいろいろ遊べそうだ。

*1:この推測アルゴリズムはEncode::Guessと同じ発想に基づく。

*2:いつでもうまく推測できるとは限らないためあしからず

*3:つまり,デコードは少なくとも二回行われるので,効率はあまり良くない