Unknown::Programming このページをアンテナに追加 RSSフィード

2007-06-26 一連

$_[0]の謎は偶然の産物

勘違いしてしまうのしょうがないね。


また、ワーク変数リファレンス値を代入後、デリファレンスすると元の値にアクセスが出来ます。

しかし、$_[0] を直接デリファレンスした $$_[0] では、元の値にアクセスが出来ません。

ワーク変数と$_[0]の中身は、同じリファレンスが入っているにも関わらず、そうなるのです(謎)。

Perlの関数引数 $_[0]の謎 - 燈明日記

元記事の現象とは下記のようなコードを書いた時の話ですが、


use strict;

sub hoge {
    print $$_[0];
}

my $str = 'aaa';
hoge(\$str);

これを実行してもprintで何も出力されないというわけですね。

実はこれ、謎でもなんでもなくて単にデリファレンスの優先順位が配列参照よりも低いから起こってるだけです。

つまり上記のコードは@_の0番目の要素をスカラデリファレンスしているのではなく$_を配列リファレンスとしてデリファレンスした0番目を参照していることになります。

ややこしいですね。

もしこれが@_じゃなくて普通の配列を使用していた場合はそもそもstrictに怒られます。


use strict;

my $str = 'aaa';
my @hoge = (\$str);

print $$hoge[0];

# Global symbol "$hoge" requires explicit package name

$hogeなんて変数宣言されてねーよ!!!!!!みたいな。

@_の場合は$_というグローバル変数がたまたま存在してるのでエラーにならないわけですね。

上記の例でも$hogeを予め宣言してたら当然エラーにはなりません。


use strict;

my $str = 'aaa';
my @hoge = (\$str);
my $hoge = ['bbb'];

print $$hoge[0]; # bbb と表示

では次にどうやって配列の要素をスカラデリファレンスするかというと、{}を使って優先順位を明示することです。


use strict;

my $str = 'aaa';
my @hoge = (\$str);

print ${ $hoge[0] }; # $hoge[0]を${}でデリファレンスする

これでわかったと思いますが、さっきまでの例は${ $hoge[0] }ではなく、${ $hoge }[0]と解釈されてたわけです。

デリファレンスはこういったややこしいこともあるのでデフォルトの動作に任せないほうがよいでしょう。

常に優先順位を明示するのがいいと思いますが、配列ハッシュ関数などのリファレンスの場合は{}ではなく->を使った方法で書くのが良いでしょう。


 my $array = [];
 my $hash  = {};
 my $code  = sub {};

 # わかりやすい
 print $array->[0];
 print $hash->{key};
 print $code->();

 # わかりにくい
 print ${$array}[0];
 print ${$hash}{key};
 print &{$code}();

この辺の話はPerlベストプラクティス(翻訳)p.245に書かれています。

11.1 逆参照(デリファレンス)

可能な限り、矢印を使って逆参照する

11.2 中かっこで囲まれた参照

プレフィックスによる逆参照以外に選択の余地がなければ、参照を中かっこで囲む

PBP万歳!ですね。

Perlベストプラクティス

Perlベストプラクティス

Catalyst::Plugin::Charsets::Japanese::Mobileのその後。

以前、Catalyst::Plugin::Charsets::Japaneseが絵文字化けたので取り急ぎでCatalyst::Plugin::Charsets::Japanese::Mobileというのを作ったのですが・・・

Catalyst::Plugin::Charsets::Japaneseで絵文字が化ける - Unknown::Programming

Unicode::Japanese使って絵文字をsjis→utf8変換すると今度はMySQLなど、DBへ突っ込んだ時に文字化けを起こしてしまいます。

なので結局cp932→utf8変換することにしました。


package Catalyst::Plugin::Charsets::Japanese::Mobile;
use strict;

use Catalyst::Plugin::Charsets::Japanese;
use base qw/Class::Data::Inheritable/;
use Jcode;
use Unicode::Japanese;
use NEXT;

__PACKAGE__->mk_classdata('charsets');
__PACKAGE__->charsets( Catalyst::Plugin::Charsets::Japanese::Mobile::Handler->new );

our $VERSION = '0.02';

sub finalize {
    my $c = shift;
    unless ( $c->response->body and not ref $c->response->body ) {
        return $c->NEXT::finalize;
    }
    unless ( $c->response->content_type =~ /^text|xml$|javascript$/ ) {
        return $c->NEXT::finalize;
    }

    my $content_type = $c->response->content_type;
    $content_type =~ s/\;\s*$//;
    $content_type =~ s/\;*\s*charset\s*\=.*$//i;
    $content_type .= sprintf("; charset=%s", $c->charsets->out->name );
    $c->response->content_type($content_type);

    my $body = $c->response->body;

    if( $c->charsets->in->name eq 'UTF-8' && utf8::is_utf8($body) ) {
        utf8::encode($body);
    }

    my $in  = $c->charsets->in->abbreviation;
    my $out = $c->charsets->out->method;
    
    $in  = 'cp932' if $in  eq 'sjis';
    $out = 'cp932' if $out eq 'sjis';
    
    $body = Jcode->new($body, $in)->$out;

    $c->response->body($body);

    $c->NEXT::finalize;
}

sub prepare_parameters {
    my $c = shift;
    $c->NEXT::prepare_parameters;

    my $in  = $c->charsets->in->method;
    my $out = $c->charsets->out->abbreviation;
    
    $in  = 'cp932' if $in  eq 'sjis';
    $out = 'cp932' if $out eq 'sjis';
    
    my $sjis_in  = $c->charsets->sjis_in($c,$out);
    my $sjis_out = $c->charsets->sjis_out($c,$out);

    for my $value ( values %{ $c->request->{parameters} } ) {
        if( ref $value && ref $value ne 'ARRAY' ) {
            next;
        }
        for ( ref($value) ? @{$value} : $value ) {
            if ( $out eq 'cp932' ) {
                $_ = Unicode::Japanese->new($_,$sjis_in)->h2zKana->$sjis_out;
                $_ = Jcode->new($_, $out)->$in;
            }
            else {
                $_ = Jcode->new($_, $out)->h2z->$in;
            }
            utf8::decode($_) if $c->charsets->in->name eq 'UTF-8';
        }
    }
}

sub setup {
    my $self = shift;
    $self->NEXT::setup(@_);
    my $setting = $self->config->{charsets} || 'UTF-8' ;
    if(ref $setting eq 'HASH') {
        $self->charsets->set_inner($setting->{in});
        $self->charsets->set_outer($setting->{out})
    } else {
        $self->charsets->set_inner($setting);
        $self->charsets->set_outer($setting);
    }
    if($self->debug){    
    $self->log->debug($self->charsets->in->name." is selected for inner code.");
    $self->log->debug($self->charsets->out->name." is selected for outer code.");
    }
}

package Catalyst::Plugin::Charsets::Japanese::Mobile::Handler;
use base qw/Catalyst::Plugin::Charsets::Japanese::Handler/;

sub sjis_out {
    my $self = shift;
    my $c    = shift;
    my $code = shift;
    if ( $code eq 'cp932' ) {
        # Catalyst::Plugin::MobileAgent
        $code =
            ( $c->req->mobile_agent->is_docomo )   ? 'sjis_imode'   :
            ( $c->req->mobile_agent->is_vodafone ) ? 'sjis_jsky'    :
            ( $c->req->mobile_agent->is_ezweb )    ? 'sjis_icon_au' :
                                                     'sjis_imode'   ;
    }
    return $code;
}

sub sjis_in {
    my $self = shift;
    my $c    = shift;
    my $code = shift;
    my $out  = $self->sjis_out($c,$code);
    if ( $code eq 'cp932' ) {
        $out =~ s/_/-/g;
    }
    return $out;
}

1;

sjisだった場合のみcp932としてエンコードするような実装になってます。

ただそれでもまだUnicode::Japaneseを使ってる部分がありますが、これは前にも書いた通り(Jcode->new($s,'cp932')->z2hで絵文字が化ける件 - Unknown::Programming)Jcodeでh2z使うと携帯絵文字が化けてしまうので仕方なく使っています。なのでCatalyst::Plugin::MobileAgentも必要です。

Jcodeのみでh2zできればもっとソースがスッキリするのですが・・・。

そろそろ一連のCatalystの話のまとめをしないと自分でもよくわかんなくなってくるな。今度まとめよう。