キーワード

プロフィール

深沢千尋

Author:深沢千尋
みなさんこんにちは、深沢千尋です。(公式ページ
文字コード【超】研究 改訂第2版NEW!」「すぐわかるPerl」「すぐわかる オブジェクト指向 Perl」の著者です。
ここでは、多くは技術的でないこと、ごくまれに技術的なことをなげやりに書いていきます。
メールは suguwakaruPerl@gmail.com まで。(アットマークは ASCII に)
Twitterはじめました。@query1000です。よろしく~

最新記事

最新コメント

最新トラックバック

月別アーカイブ

カテゴリ

検索フォーム

RSSリンクの表示

リンク

ブロとも申請フォーム

QRコード

QRコード

スポンサーサイト

上記の広告は1ヶ月以上更新のないブログに表示されています。
新しい記事を書く事で広告が消せます。

「まるごとEncode」解題(4)use bytes; の迷宮

つづき。

原記事から引用。
「このレキシカルプラグマ(use utf8;)は「ここから先、文字列リテラルは UTF-8 ですよ」ということを示しているのです。そうでない「下位互換モード」を強制するにはどうしたらよいかというと、「use bytes;」を使います。」
ということで、use bytes; の使い方を練習する。

まず、原記事に載っている同様、UTF-8 を出力するプログラムを動かしてみる。
文字列、注釈等は変えている。

#! perl
# bytesExp.pl -- use bytes を使ってみる

use strict;
use warnings;

# ブロックの外側ではUTF-8文字単位で解釈される
use utf8;

{
# ブロックの内側ではバイトモードが強制される(ようにしたい)
use bytes;
my $text = 'abc漢字ひらがなカタカナ';
print "inside:", substr($text, 9, 12)), "\n";
# 10バイト目から12バイト、ひらがなと表示されたい
}

my $text = 'abc漢字ひらがなカタカナ';
binmode STDOUT, ":utf8";
print "outside:", substr($text, 5, 4), "\n";
# 6文字目から4文字、ひらがなと表示されたい

__END__

実行。

C:\Documents and Settings\you\デスクトップ\Marugoto>bytesExp.pl
inside:縺イ繧峨′縺ェ
outside:縺イ繧峨′縺ェ

ぼくぐらいになってくるとこれぐらいの UTF-8 が CP932に化けているのは読めるようになってくるが(ウソ)これはきっと成功している。
ファイルにリダイレクトして中身を UTF-8 で見ると、ちゃんと

inside:ひらがな
outside:ひらがな

を得られた。

さて、上のプログラムでは、下の、use bytes が効いていない print には

binmode STDOUT, ":utf8";
print "outside:", substr($text, 5, 4), "\n";

と binmode を使っている。
これは、use bytes; スコープの外の substr は Perl 内部表現を返すので encode してやる必要があるからだ。

しかるに、ブロックの中の print は binmode をしていない。
print "inside:", substr($text, 9, 12)), "\n";

これは、use bytes; 影響下の substr はバイトストリームを返すので encode する必要はないからである。

では、同じプログラムを Windows で動かしてみようとすると、これがうまくいかない。

#! perl
# bytesExp.pl -- use bytes を使ってみる

use strict;
use warnings;
binmode STDOUT, ':encoding(cp932)';

# ブロックの外側ではUTF-8文字単位で解釈される
use utf8;

{
# ブロックの内側ではバイトモードが強制される(ようにしたい)
use bytes;
my $text = 'abc漢字ひらがなカタカナ';
print "inside:", Encode::decode('UTF-8', substr($text, 9, 12)), "\n";
# 10バイト目から12バイト、ひらがなと表示されたい
}

my $text = 'abc漢字ひらがなカタカナ';
print "outside:", substr($text, 5, 4), "\n";
# 6文字目から4文字、ひらがなと表示されたい

__END__

実行してみる。

C:\Documents and Settings\you\デスクトップ\Marugoto>bytesExp.pl
Cannot decode string with wide characters at C:/strawberry/perl/lib/Encode.pm line 174.

えっ174行もプログラム書いたっけ~ と思ったけど、これは decode を実行する Encode モジュールの中でコケている。
decode は以下の行で呼んでいる。

print "inside:", Encode::decode('UTF-8', substr($text, 5, 4)), "\n"; # ひらがなと表示されたい

これは例によって binmode で cp932 に切り替えて print 出力しているのだが、そうするとそのとき encode が起きるので、その前に decode してみた。
(ちなみに今回は use Encode; せず、Encode::decode と書いてみた。)

なぜ decode したかというと、use bytes; 配下にある文字列はバイトストリームであると思っていたからだ。
しかるに、上のエラーメッセージはなんだろうか。

Cannot decode string with wide characters at C:/strawberry/perl/lib/Encode.pm line 174.

ワイド文字をデコードできない、と言ってきている。
ワイド文字とは何か。
これは、第2回で、

use utf8; # スクリプトは UTF-8 で保存
...
#binmode STDOUT, ':encoding(cp932)'; # 出力はCP932に変更
...
print substr($text, 5, 4);

というプログラムが print で怒られたときと同じである。
(binmode は怒られる実験のために注釈化)
utf8 配下の Perl 内部表現文字列を、binmodeなしでそのまま出力しようとしたので、あらワイド文字ね、と怒られたのだ。

とすると上の

binmode STDOUT, ':encoding(cp932)';
use utf8;
{
use bytes;
my $text = 'abc漢字ひらがなカタカナ';
print "inside:", Encode::decode('UTF-8', substr($text, 5, 4)), "\n"; # ひらがなと表示されたい

はどこがダメなんだろうか。

ここでは use bytes を use utf8 の取り消しのように使っているが、use utf8 の取り消しは no utf8 であって、use bytes はもうひとつ別の何か違うもののようである。

ではなんだろうか。
perldocに聞いてみる。

C:\Documents and Settings\you\デスクトップ\Marugoto>perldoc bytes # 以下、翻訳は深沢
SYNOPSIS 概要
use bytes;
... chr(...); # or bytes::chr
... index(...); # or bytes::index
... length(...); # or bytes::length
... ord(...); # or bytes::ord
... rindex(...); # or bytes::rindex
... substr(...); # or bytes::substr
no bytes;

DESCRIPTION 説明
The "use bytes" pragma disables character semantics for the rest of the
lexical scope in which it appears. "no bytes" can be used to reverse the
effect of "use bytes" within the current lexical scope.

"use bytes" プラグ間はこれが現れたレキシカルスコープの残りの部分において、
文字セマンティクスを無効化するものである。"no bytes" はそのカレント
スコープにおいて、"use bytes" の効果を打ち消すことができる。

Perl normally assumes character semantics in the presence of character
data (i.e. data that has come from a source that has been marked as
being of a particular character encoding). When "use bytes" is in
effect, the encoding is temporarily ignored, and each string is treated
as a series of bytes.

Perl は通常、存在する文字データ(すなわち、ソースから得られた文字列であって、
特定の文字エンコーディングにマークされているもの)は文字セマンティクスを仮定する。
"use bytes" が有効な場合、エンコーディングは一時的に無視され、個々の文字列は
バイトの連なりとして扱われる。

つまり、これは、文字列の Perl 内部表現性を変えるのではなく、それを扱う以下の関数

use bytes;
... chr(...); # or bytes::chr
... index(...); # or bytes::index
... length(...); # or bytes::length
... ord(...); # or bytes::ord
... rindex(...); # or bytes::rindex
... substr(...); # or bytes::substr
no bytes;

の挙動を変えるものだったのだ。

ということで、ブロック内も文字列は Perl 内部表現であり続けるようなので decode を取ってみる。

#! perl
# bytesExp.pl -- use bytes を使ってみる

use strict;
use warnings;
binmode STDOUT, ':encoding(cp932)';

# ブロックの外側ではUTF-8文字単位で解釈される
use utf8;

{
# ブロックの内側ではバイトモードが強制される(ようにしたい)
use bytes;
my $text = 'abc漢字ひらがなカタカナ';
print "inside:", substr($text, 9, 12), "\n";
# 10バイト目から12バイト、ひらがなと表示されたい
}

my $text = 'abc漢字ひらがなカタカナ';
print "outside:", substr($text, 5, 4), "\n";
# 6文字目から4文字、ひらがなと表示されたい

__END__

どうだろう。

C:\Documents and Settings\you\デスクトップ\Marugoto>bytesExp.pl
"\x{0081}" does not map to cp932 at C:\Documents and Settings\you\デスクトップ\Marugoto\bytesExp.pl line 17.
"\x{0082}" does not map to cp932 at C:\Documents and Settings\you\デスクトップ\Marugoto\bytesExp.pl line 17.
"\x{0089}" does not map to cp932 at C:\Documents and Settings\you\デスクトップ\Marugoto\bytesExp.pl line 17.
"\x{0081}" does not map to cp932 at C:\Documents and Settings\you\デスクトップ\Marugoto\bytesExp.pl line 17.
"\x{008c}" does not map to cp932 at C:\Documents and Settings\you\デスクトップ\Marugoto\bytesExp.pl line 17.
"\x{0081}" does not map to cp932 at C:\Documents and Settings\you\デスクトップ\Marugoto\bytesExp.pl line 17.
inside:a\x{0081}2a\x{0082}\x{0089}a\x{0081}\x{008c}a\x{0081}a
outside:ひらがな

うわっすごい怒られてしまった。

これは以前、

binmode STDOUT, ':encoding(cp932)'; # 出力はCP932に変更
my $text = 'abc漢字ひらがなカタカナ';
print substr($text, 9, 12);

でやられたパターンと同じである。
何回も怒られていると怒られても動じなくなるね! (^^)

出力された文字は
a
\x{0081}
2
a
\x{0082}
\x{0089}
a
\x{0081}
\x{008c}
a
\x{0081}
a

で、これも UTF-8 の「ひらがな」が ISO 8859-1 として誤解釈されている。

やはりデコードしないといけないのか。

まとめると、

Encode::decode('UTF-8', substr($text, 9, 12))

だと decode に substr($text, 9, 12) はもう内部表現だからデコードできないよといわれ、

substr($text, 9, 12)

だと後続の print で ISO 8859-1 扱いを受けるという始末である。
どうすればいいのだろうか。

フト思い立って、substr の結果を一度 encode してから decode してみる。
こうだ。

#! perl
# bytesExp.pl -- use bytes を使ってみる

use strict;
use warnings;
binmode STDOUT, ':encoding(cp932)';

# ブロックの外側ではUTF-8文字単位で解釈される
use utf8;

{
# ブロックの内側ではバイトモードが強制される(ようにしたい)
use bytes;
my $text = 'abc漢字ひらがなカタカナ';
print "inside:", Encode::decode('UTF-8', Encode::encode("UTF-8", substr($text, 9, 12))), "\n";
# 10バイト目から12バイト、ひらがなと表示されたい
}

my $text = 'abc漢字ひらがなカタカナ';
print "outside:", substr($text, 5, 4), "\n";
# 6文字目から4文字、ひらがなと表示されたい

__END__

なんかもう、すげえよな。
では実行。

C:\Documents and Settings\you\デスクトップ\Marugoto>bytesExp.pl
inside:カタカナ
outside:ひらがな

ううん、見た目上エラーも出ず、何らかの文字列が表示されているが、ダメだ。

my $text = 'abc漢字ひらがなカタカナ';
print "inside:", Encode::decode('UTF-8', Encode::encode("UTF-8", substr($text, 9, 12))), "\n";

'abc漢字ひらがなカタカナ' は abc漢字の時点で9バイトで、ひらがなはそこから12バイトだ。
だからひらがなと出て欲しかったのだが、カタカナと出てしまった。
先頭から9バイトではなくて、9文字数えてしまっている。
つまり、bytesモードが効いていない。

そこで、perldoc で得られた知見を元に bytes::substr にしてみよう。

#! perl
# bytesExp.pl -- use bytes を使ってみる

use strict;
use warnings;
binmode STDOUT, ':encoding(cp932)';

# ブロックの外側ではUTF-8文字単位で解釈される
use utf8;

{
# ブロックの内側ではバイトモードが強制される(ようにしたい)
use bytes;
my $text = 'abc漢字ひらがなカタカナ';
print "inside:", Encode::decode('UTF-8', Encode::encode("UTF-8", bytes::substr($text, 9, 12))), "\n";
# 10バイト目から12バイト、ひらがなと表示されたい
}

my $text = 'abc漢字ひらがなカタカナ';
print "outside:", substr($text, 5, 4), "\n";
# 6文字目から4文字、ひらがなと表示されたい

__END__

実行。

C:\Documents and Settings\you\デスクトップ\Marugoto>bytesExp.pl
"\x{0081}" does not map to cp932 at C:\Documents and Settings\you\デスクトップ\Marugoto\bytesExp.pl line 15.
"\x{0082}" does not map to cp932 at C:\Documents and Settings\you\デスクトップ\Marugoto\bytesExp.pl line 15.
"\x{0089}" does not map to cp932 at C:\Documents and Settings\you\デスクトップ\Marugoto\bytesExp.pl line 15.
"\x{0081}" does not map to cp932 at C:\Documents and Settings\you\デスクトップ\Marugoto\bytesExp.pl line 15.
"\x{008c}" does not map to cp932 at C:\Documents and Settings\you\デスクトップ\Marugoto\bytesExp.pl line 15.
"\x{0081}" does not map to cp932 at C:\Documents and Settings\you\デスクトップ\Marugoto\bytesExp.pl line 15.
inside:a\x{0081}2a\x{0082}\x{0089}a\x{0081}\x{008c}a\x{0081}a
outside:ひらがな

もうわかった。
これは decode せずに encode したからだ。
現状はこうなっている。

print "inside:", Encode::decode('UTF-8', Encode::encode("UTF-8", bytes::substr($text, 9, 12))), "\n";

このうち、内側の Encode::encode は、substr がバイトストリームではなくて Perl 内部表現だという疑いの結果付けられたもので、それが bytes::substr である今、もう必要ないのではないか。
つまり、こうだ。

print "inside:", Encode::decode('UTF-8', bytes::substr($text, 9, 12)), "\n";

これで言ってみよう~

#! perl
# bytesExp.pl -- use bytes を使ってみる

use strict;
use warnings;
binmode STDOUT, ':encoding(cp932)';

# ブロックの外側ではUTF-8文字単位で解釈される
use utf8;

{
# ブロックの内側ではバイトモードが強制される(ようにしたい)
use bytes;
my $text = 'abc漢字ひらがなカタカナ';
print "inside:", Encode::decode('UTF-8', bytes::substr($text, 9, 12)), "\n";
# 10バイト目から12バイト、ひらがなと表示されたい
}

my $text = 'abc漢字ひらがなカタカナ';
print "outside:", substr($text, 5, 4), "\n";
# 6文字目から4文字、ひらがなと表示されたい

__END__

実行。

C:\Documents and Settings\you\デスクトップ\Marugoto>bytesExp.pl
inside:ひらがな
outside:ひらがな

アーうまくいった。
疲れた。
でも、せっかく use bytes; しているのに、なぜ bytes::substr を呼ばなければならないのか。
これでは意味がない。

最初の UTF-8 版では、このプログラムは UTF-8 で出力しているのでこうなっている。

use bytes;
my $text = 'abc漢字ひらがなカタカナ';
print "inside:", substr($text, 9, 12)), "\n";

このように、単純に substr の結果をそのまま print している。
しかるに Windows 版(CP932版)では

print "inside:", Encode::decode('UTF-8', bytes::substr($text, 9, 12)), "\n";

のように、bytes::substr を decode している。
これは、binmode で print 時に encode されるから、バイトストリームを Perl 内部表現に戻したのだ。

その前はこうしていた。

print "inside:", Encode::decode('UTF-8', Encode::encode("UTF-8", substr($text, 9, 12))), "\n";

substr が Perl 内部表現なので encode し、それを decode してから binmode で出力していた。
しかもこの substr は正しくバイトモードで起動せず、文字を数えていた。
わかりますか。

実は、現時点の use bytes; には疑問点があり、ある文字列関数Aが、別の関数Bの引数として渡されるとき、その関数Aには効果が及ばないようである。

次のプログラムでよくわかる。

#! perl
# expBytes.pl -- Enperiment of bytes
# 本件について OKWave でご教示をいただきました。お世話になりました。

use strict;
use warnings;
use utf8;
use bytes;
use Encode;

my $text = '漢字ひらがなカタカナEnglish';
print (Encode::is_utf8($text) ? "text is $text and utf8\n" : "text is $text and no utf8\n");

my $subtext = substr($text, 3, 3);
print (Encode::is_utf8($subtext) ? "subtext is $subtext and utf8\n" : "subtext is $subtext and no utf8\n");

my $subtext2 = returnStr(substr($text, 3, 3));
print (Encode::is_utf8($subtext2) ? "subtext2 is $subtext2 and utf8\n" : "subtext2 is $subtext2 and no utf8\n");

my $subtext3 = returnStr(bytes::substr($text, 3, 3));
print (Encode::is_utf8($subtext3) ? "subtext3 is $subtext3 and utf8\n" : "subtext3 is $subtext3 and no utf8\n");

sub returnStr {
shift
}

結果はこうなる。

text is 漢字ひらがなカタカナEnglish and utf8
subtext is 字 and no utf8
subtext2 is らがな and utf8
subtext3 is 字 and no utf8

Encode::is_utf8 はある文字列が Perl の内部表現かどうか調べるものだが、使用は推奨されていない。

さて、上記のプログラムはすべて use utf8; と use bytes; の配下である。

$text は Perl 内部表現である。

素の substr は 3, 3 を渡されて4バイト目から3バイトの「字」を返していて、出力 $subtext2 もバイトストリームである。

returnStr という何もしないラッパー関数でくるまれてしまった substr は 3, 3 を渡されて4文字目から3文字の「らがな」を返している。
しかも出力 $subtext3 は Perl 内部表現である。

同様に returnStr でくるまれた bytes::substr はバイトモードで動作して出力 $subtext3 もバイトストリームだ。

このように、substr は関数の引数になったとたんに、bytes が効かなくなる。
これは昨日 perlbug に起票してみた。

そうと分かれば最初のプログラムをもっと簡潔に改良でき。

#! perl
# bytesExp.pl -- use bytes を使ってみる

use strict;
use warnings;
binmode STDOUT, ':encoding(cp932)';

# ブロックの外側ではUTF-8文字単位で解釈される
use utf8;

{
# ブロックの内側ではバイトモードが強制される(ようにしたい)
use bytes;
my $text = 'abc漢字ひらがなカタカナ';

my $subtext = substr($text, 9, 12); # 一度変数に取ってみる

print "inside:", Encode::decode('UTF-8', $subtext), "\n";
# 10バイト目から12バイト、ひらがなと表示されたい
}

my $text = 'abc漢字ひらがなカタカナ';
print "outside:", substr($text, 5, 4), "\n";
# 6文字目から4文字、ひらがなと表示されたい

__END__

実行結果:

C:\Documents and Settings\you\デスクトップ\Marugoto>bytesExp.pl
inside:ひらがな
outside:ひらがな

substr の結果を直接 decode に渡さず、いったん変数に取ってから decode に渡している。
これで substr の挙動は無事 bytes 化され、10 バイト目から 12 バイトの「ひらがな」が返されるので、そのバイトストリームを decode して Perl 内部表現に変えてから print すると、binmode によって正しく CP932 で表示される。
めんどうだなあ。

ということで、use bytes が文字列関数をバイトモードで実行し、バイトストリームの結果を返すが、現在ちょっと不具合があって、別の関数の引数で使うと効かなくなってしまうということが分かった。

と、ここまで読んできてあっと驚きなされ、use bytes; は Encode::is_utf8 同様、非推奨機能だそうだ。
・バイトストリームが欲しければ encode する
・Perl 内部表現が欲しければ decode する
・UTF8 フラグの存在は忘れろ
だって。

perlunifaq - Perl Unicode FAQ
​http://perldoc.jp/docs/perl/5.10.0/perlunifaq.pod​

2008-06-25 - daily dayflower
​http://d.hatena.ne.jp/dayflower/20080625​
スポンサーサイト

<< 「まるごとEncode」解題(5)今さら decode() の解説されても・・・ | ホーム | 「まるごとEncode」解題(3)use utf8;のスコープ >>


コメント

コメントの投稿


管理者にだけ表示を許可する

 ホーム 


上記広告は1ヶ月以上更新のないブログに表示されています。新しい記事を書くことで広告を消せます。