スキップしてメイン コンテンツに移動

Perl 5 to 6 - Unicode

これはMoritz Lenz氏のWebサイトPerlgeek.deで公開されているブログ記事"Perl 5 to 6" Lesson 17 - Unicodeの日本語訳です。

原文はCreative Commons Attribution 3.0 Germanyに基づいて公開されています。

本エントリにはCreative Commons Attribution 3.0 Unportedを適用します。

Original text: Copyright© 2008-2010 Moritz Lenz

Japanese translation: Copyright© 2011 SATOH Koichi

NAME

"Perl 5 to 6" Lesson 17 - Unicode

SYNOPSIS

(なし)

DESCRIPTION

Perl5のUnicodeモデルは大きな弱点に悩んでいました: バイナリとテキストデータに同じ型を使っていたのです。 プログラムがネットワークソケットから512バイト読み込んだとすると、それは当然バイト列になります。しかしそれに対して(Perl5で)ucを呼ぶとテキストとして扱われます。 推奨されている方法は最初にバイト列をデコードすることですが、サブルーチンがそれを引数として受け取る段階では、それがエンコードされているのかどうか、つまりblobとして扱うべきかテキストとして扱うべきか確実に判断することは不可能です。

一方、Perl6は単なるバイトのコレクションであるBuf型を提供します。またStrは論理的な文字のコレクションです。

論理的な文字という用語にはいささか説明が必要です。正確に言うと、Strは様々な水準の見方ができるオブジェクトです: ByteCodePoint(Unicodeコンソーシアムが番号を割り当てたものすべて)、Grapheme(書記素; 文字として目に見えるもの)、CharLingua(言語定義文字)。

例えば16進数表記で61 cc 80は(当然)3バイトのバイト列を構成します。しかしこれは2つのコードポイントLATIN SMALL LETTER A(U+0041)とCOMBINING GRAVE ACCENT(U+0300)、あるいは1つの書記素(ブログシステムないしあなたのブラウザが文字を殺してなければ、àのように見えます)を表しているものと見ることもできます。

したがって文字列の長さを単純に調べることはできず、特定の見方に拠る長さを調べる必要があります:

$str.bytes;
$str.codes;
$str.graphs;

charsメソッドもあり、これは現在のUnicode水準(use byteのようなプラグマで指定でき、デフォルトは書記素)における長さを返します。

Perl5ではしばしば間違ってバイト列を文字列に連結してしまう問題が起きます。 Perl6でそのような問題に悩んだときは、文字列連結演算子をオーバーロードして簡単に問題が発生した位置を特定できます:

sub GLOBAL::infix:<~> is deep (Str $a, Buf $b)|(Buf $b, Str $a) {
    die "Can't concatenate text string «"
        ~ $a.encode("UTF-8")
          "» with byte string «$b»\n";
}

エンコードとデコード

IOシステムの仕様は非常に基本的なもので、エンコードとデコード用のレイヤはまだ定義されていません。この記事にSYNOPSISがないのはそのためです。 そのような機構が用意されるのは間違いなく、コードは次のようなものになると思われます:

my $handle = open($filename, :r, :encoding<UTF-8>);

正規表現とUnicode

正規表現はUnicode水準を指定する修飾子を取ることができ、m:codes/./は丁度1個のコードポイントにマッチします。修飾子を指定しない場合は現在のUnicde水準が使われます。

(文字にマッチする)\wのような文字クラスはUnicode標準にしたがって動作します。 修飾子として大文字小文字の無視(:i)やアクセントの無視(:a)、置換演算子の置換結果に大文字小文字やアクセントを反映するもの(:samecase:sameaccent、省略形は:ii:aa)があります。

MOTIVATION

今日のほとんどのツールやプログラミング言語で文字列を正しく処理するのはとても難しいことです。 Perl5で書かれたWebアプリケーションを作っているとして、長い単語がレイアウトを崩してしまわないように分割したいとしましょう。 組み込みのsubstrを使ったりすると、意図せずして書記素が泣き別れになるかも知れません。

Perl6は書記素レベルの文字列操作を組み込みでサポートした最初の主要言語になります。 この文字列操作機能はUnicodeを使う上でほとんどの悩みを取払い、(正規表現と連携して)Perl6を文字列処理における最も強力な言語の1つにします。

テキスト用とバイト列用の個別のデータ型はデバッグとイントロスペクションを非常に容易にします。

SEE ALSO

http://perlcabal.org/syn/S29.html#Str

コメント

このブログの人気の投稿

Perl の新 class 構文を使ってみる

Perl 5 のオブジェクト指向機能は基本的には Python の影響を受けたものだが、データを名前空間 (package) に bless する機構だけで Perl 4 以来の名前空間とサブルーチンをそのままクラスとメソッドに転換し第一級のオブジェクト指向システムとした言語設計は驚嘆に価する。 実際この言語のオブジェクトシステムは動的型付言語のオブジェクト指向プログラミングに要求されるおよそあらゆる機能を暗にサポートしており、CPAN には Moose を筆頭とした屋下屋オブジェクトシステムが複数存在しているがその多くは Pure Perl ライブラリである。つまり「やろうと思えば全部手書きで実現できる」わけである。 そういうわけで Perl のオブジェクト指向プログラミングサポートは機能面では (静的型検査の不在という現代的には極めて重大な欠如を除けば) 申し分ないのだが、しかし Moose その他の存在が示しているように一つ明らかな欠点がある。記述の冗長さだ。 コンストラクタを含むあらゆるメソッドは第一引数としてレシーバを受ける単なるサブルーチンとして明示的に書く必要があるし、オブジェクトのインスタンス変数 (a.k.a. プロパティ / データメンバ) は bless されたデータに直接的ないし間接的に プログラマ定義の方法 で格納されるためアクセス手段は実装依存である。これはカプセル化の観点からは望ましい性質だが、他者の書いたクラスを継承するときに問題となる。ある日データ表現を変更した親クラスがリリースされると突然自分の書いた子クラスが実行時エラーを起こすようになるわけだ。 そうならないためにはインスタンス変数へのアクセスに (protected な) アクセサを使う必要があるのだが、そのためには親クラスが明示的にそれらを提供している必要があるし、そもそも Perl にはメソッドのアクセス修飾子というものがないので完全な制御を与えるならばオブジェクトの内部状態がすべて public になってしまう。 そのような事情もあり、特にパフォーマンスが問題にならないようなアプリケーションコードでは Moose のようなリッチな語彙を提供するオブジェクトシステムを使うことが 公式のチュートリアルでも推奨 されてきた。Perl コアのオブジェクトシステムの改良は...

去る6月に Perl 5.32.0 がリリースされたので差分を把握するために perldelta を読んだ件

要旨 Perl 5 メジャーバージョンアップの季節がやって来たのでまともな Perl プログラマの嗜みとして perldelta を読んだ。 今回は有り体に言えばルーティン的なリリースで、言語コアの拡張は他言語にも見られる構文が実験的に入ったくらいで大きな変化はない。新機能は RegExp の拡充が主である。 比較的重要と思われる変更点を抜粋する。 新機能 isa 演算子 実験的機能。Python とか Java における isinstance とか instanceof 。 これまでも UNIVERSAL::isa があったが、これはメソッドなのでレシーバにオブジェクトでもクラスでもない値 (i.e., 未定義値 / bless されていないリファレンス) を置くと実行時エラーが起きるのが問題だった: package Foo { use Moo; } package Bar { use Moo; extends ' Foo ' ; } package Baz { use Moo; } use feature qw/ say / ; sub do_something_with_foo_or_return_undef { my ( $foo ) = @_ ; # Returns safely if the argument isn't an expected instance, in mind. return unless $foo -> isa ( ' Foo ' ); ...; } # OK. do_something_with_foo(Bar->new); # |undef| is expected in mind, but actually error will be thrown. do_something_with_foo( undef ); これを避けるために今までは Scalar::Util::blessed を併用したりしていたわけだが、 isa 演算子は左辺が何であっても意味のある値を返すのでよりシンプルになる: # True +( bless +{} ...

Project Euler - Problem 35

問題 原文 How many circular primes are there below one million? 日本語訳 100万未満の巡回素数は何個か? 解答 回転させた数値がすべて素数ということは、すべての桁が奇数でなければいけません(ただし2を除く)。 追記 匿名氏にコメントでご指摘頂いたのでコードを一部修正しました。 いずれかの桁に5がある場合も、回転させると必ず5の倍数が現れるので除外できます。 もっと追記 前の修正に間違いが入っているのをご指摘頂いたので修正しました。 5自体は素数なので、巻き添えで除外してはいけません。 #!/usr/bin/env perl use strict; use warnings; use feature qw/say state/; use List::MoreUtils qw/all none/; sub is_prime($) { state %memos; my $n = shift; return 0 if $n < 2; return 1 if $n == 2; return 1 if $n == 3; return $memos{$n} if exists $memos{$n}; $memos{$n} = none { $n % $_ == 0 } 2 .. sqrt $n; } sub rotate($) { my $n = shift; substr($n, 1) . substr($n, 0, 1); } sub rotations($) { my $n = shift; my %seen = ($n => 1); $seen{$n} = 1 until exists $seen{$n = rotate $n}; keys %seen; } sub is_circular_prime($) { state %memos; my $n = shift; return 0 if $n =~ /[024568]/ and $n != 2 and $n != 5; return $memos{$n} if exists $memos{$n}; my ...

Project Euler - Problem 27

問題 しばらく止まってましたが今日から再開。 原文 Considering quadratics of the form: n 2 + an + b, where |a| < 1000 and |b| < 1000 Find the product of the coefficients, a and b, for the quadratic expression that produces the maximum number of primes for consecutive values of n, starting with n = 0. 日本語訳 |a| < 1000, |b| < 1000 として以下の二次式を考える (ここで|a|は絶対値): n 2 + an + b n=0から始めて連続する整数で素数を生成したときに最長の長さとなる上の二次式の, 係数a, bの積を答えよ. 解答 最大探索範囲は-999 <= a <= 999、-999 <= b <= 999なので、およそ4,000,000通りの係数の組合せを試すことになります。組合せ毎に数列を生成して、それが素数か判定するわけですからたまりません。簡単な検討を加えて範囲を絞りましょう。 与えられた二次式をf(n)とおくと、f(0) = b、f(1) = a + b + 1です。 f(n)が長さ2以上の素数列を生成するならこれらは素数ですから、次のことがいえます: bは素数である a + b + 1は素数である b = 2のとき、aは偶数である それ以外のとき、aは奇数である 素数判定関数 is_prime には同じ引数が与えられることがよくあるのでメモ化しています。 #!/usr/bin/perl use strict; use warnings; use feature qw/say/; sub prime_seq_len($$) { my ($coeff_a, $coeff_b) = @_; my $len = 0; my $n = 0; $len++, $n++ while is_prime($n * ($n + $coeff_a) ...

Perl 7 より先に Perl 5.34 が出るぞという話

Perl 5 の次期バージョンとして一部後方互換でない変更 (主に間接オブジェクト記法の削除とベストプラクティスのデフォルトでの有効化) を含んだメジャーバージョンアップである Perl 7 がアナウンスされたのは昨年の 6 月 のことだったが、その前に Perl 5 の次期周期リリースである Perl 5.34 が 5 月にリリース予定 である。 現在開発版は Perl 5.33.8 がリリースされておりユーザから見える変更は凍結、4 月下旬の 5.33.9 で全コードが凍結され 5 月下旬に 5.34.0 としてリリース予定とのこと。 そういうわけで事前に新機能の予習をしておく。 8進数数値リテラルの新構文 見た瞬間「マジかよ」と口に出た。これまで Perl はプレフィクス 0 がついた数値リテラルを8進数と見做してきたが、プレフィクスに 0o (zero, small o) も使えるようになる。 もちろんこれは2進数リテラルの 0b や 16進数リテラルの 0x との一貫性のためである。リテラルと同じ解釈で文字列を数値に変換する組み込み関数 oct も` 新構文を解するようになる。 昨今無数の言語に取り入れられているリテラル記法ではあるが、この記法の問題は o (small o) と 0 (zero) の区別が難しいことで、より悪いことに大文字も合法である: 0O755 Try / Catch 構文 Perl 5 のリリース以来 30 年ほど待たれた実験的「新機能」である。 Perl 5 における例外処理が特別な構文でなかったのは予約語を増やさない配慮だったはずだが、TryCatch とか Try::Tiny のようなモジュールが氾濫して当初の意図が無意味になったというのもあるかも知れない。 use feature qw/ try / ; no warnings qw/ experimental::try / ; try { failable_operation(); } catch ( $e ) { recover_from_error( $e ); } Raku (former Perl 6) だと CATCH (大文字なことに注意) ブロックが自分の宣言されたスコープ内で投げられた例外を捕らえる...