Unicode文字列は「flagged utf8」UTF-8バイト列は「flaggedじゃないutf8?」

perlでutf8にひっかかっては、ブックマークした記事を読見なおしたり、昔買った「まるごとPerl」を読み直したり、改めて検索したりして、何かいつもわかったようなわからないような感じになってるので、今後また見るようにメモ書きを残すことにした。

知ってること

入り口で decode して、内部ではすべて flagged utf8 で扱い、出口で encode する。これがすべてです!

知りたいこと

  • モジュールを自分で書くとしたらutf8フラグが立っている(decodeしてある)ものを受け取るのが良いのか、フラグを落として(encodeして)もらうのが良いのか
    • モジュールに引数を渡す場合は上でいう出口に当たるのかな?と思い。

調べたこと

これは自分のCPANモジュールなんかでも、「引数は Unicode 文字列または UTF-8 バイト列を渡してくれればいい具合に処理しますよ」という目的でつけていたんだけど、最近やはりこれは間違いである、という認識に達した。

PerlUnicode 文字列を扱うときのマナー

* 外界からやってくるものは,原則的に*4すべて byte stream です。
* 外界へは,原則的にすべて byte stream で出力します。
o これらの byte stream は,Shift_JIS 文字列を表すのかもしれないし,EUC-JP 文字列を表すのかもしれないし,etc etc ...
* Encode::decode() で byte stream から Unicode 文字列に変換することができます。
* Encode::encode() で Unicode 文字列から byte stream に変換することができます。

    • Unicode文字列」と「byte stream」が上の「Unicode文字列」と「UTF-8バイト列」に対応していそう(byte streamがUTF-8文字列の場合の。)
      • Encode::decode()でbyte streamからUnicode文字列と言っているので、「入り口で decode して、内部ではすべて flagged utf8 で扱い、」に従うと、Unicode文字列は「flagged utf8」のことっぽい
      • ということはbyte streamは「flaggedじゃないutf8(この言い方が合ってるかわからないけど)」っぽい
    • というか、この図超わかりやすい。ステキ。

ここまででわかったこと

  • モジュールは「flagged utf8」でも「flaggedじゃないutf8」でも両方受け取れるのが良さそう
    • ただ「最近これは間違いであるという認識に達した」って書いてある
    • もうちょっと記事を読み進める

再度調べた

よって uri_for などの関数・メソッドは「Unicode 文字列を受け付ける(UTF-8 フラグあり、または latin-1 エンコード)」ものと「バイト列を受け付ける」ものとで別のメソッドにするなり、パラメータで制御するなりする、というのがベストな解法ということになる。

    • 両方受け取るとしてもメソッド変えるか、パラメータによってどちらで受け取るか決めるのが良いのかな?

やはり、プログラムやモジュールでは「この文字列は Unicode 文字列であるかバイト列であるかわからない」という入出力パラメータを扱うべきではなく、つねに「Unicode 文字列」や「バイト列」であることを明確にしておくべき。そうすれば decode_utf8 や utf8::upgrade などで明確にフラグをたてることができる。

    • おぉ。やっぱり明確に決めておくのがいいみたい。
    • どっちで受け取ってもいいけど、両方を一つのメソッドで受けるのはよくないと。
    • 具体的な例がどこかにないかな?
  • Lingua::JA::Hepburn::Passport

unless (utf8::is_utf8($string)) {
croak "romanize(string): should be UTF-8 flagged string";
}

    • あった。「utf8フラグ立ってないと受け取らないよ」って書いてある。

他の例も探す

同じことができるモジュールとして id:takefumi 作の Lingua::JA::Regular というものがあるのですが、これは EUC-JP 前提なので、昨今の「内部コードは flagged utf8」という流れからすると、無駄に二回文字コードの変換をはしらせなくてはならず、使いづらかったので、新たに unicode 前提でつくりなおしました。

    • 「内部コードは flagged utf8」って書いてあった。
    • ソースを見てみたけど、use utf8;で内部をflagged utf8で扱う以外には特にflagged utf8には触れてない。flagged utf8が前提だからencode/decode関係の処理は何もしないということなのかな。
    • 「入り口で decode して、内部ではすべて flagged utf8 で扱い、出口で encode する。これがすべてです!」で調べ始まって、結局id:tokuhiromの記事に戻ってきた。というか、昔この記事読んだ記憶ある。tokuhiromの日記だけ読んでたらいいんじゃないかと思ってしまう。

もっと他の例あるかな?

Web アプリケーションの開発者として

なんだかごちゃごちゃと難しそうだけど,じゃあどうすればいいのか。あくまで個人的な指針ですが,

* アプリケーションのコアでは UTF8 フラグつき文字列のみ扱う
o ASCII 範囲や ISO-8859-1 範囲の文字列についても Encode モジュール等で UTF8 フラグつきにできますから
* 入出力時にエンコーディングを変換する
* (今回の知見から)既存モジュールとやりとりするときも,仕様に沿ってよしなに変換する

でやってます。

    • utf8::is_utf8の話の流れで見たページにも書いてあった
    • flagged utf8で扱うのが良さそう
    • 「Encode モジュール等で UTF8 フラグつきにできますから」の意味は、utf8::is_utf8で触れられている「\x{e9}」みたいに「Latin-1 の範囲(\x{0000} 〜 \x{00FF})に収まる文字列」ってことかな。
      • 例えば\x{e9}は、その範囲内にあるのでuse utf8;をしてもflagged utf8にならないのね。この時にutf8::upgradeを使うといいのかな。

まとめ

  • 自分でモジュール書く場合はflagged utf8前提(latin-1の範囲だとflaggedじゃないけど)にする。それで問題が出たらその時また調べ直して考える。
    • とりあえず日本語はlatin-1の範囲内じゃないからflagged前提にしとけば良さそうなのであんまり気にしないことにする
    • なのでutf8::upgradeとかは考えない
  • どのページもずっと前にブックマークしたページだけど全然理解してないまま放っておいてた。今もちゃんと理解できてない気がするけどとりあえずわかる範囲では消化できた感じがする。