Skip to content

Instantly share code, notes, and snippets.

@shkumagai
Last active January 31, 2018 09:53
Show Gist options
  • Save shkumagai/5270218 to your computer and use it in GitHub Desktop.
Save shkumagai/5270218 to your computer and use it in GitHub Desktop.
using unicode in erlang [ja]
original:Using Unicode in Erlang
source:STDLIB User's Guide Version 1.19.1
url:http://www.erlang.org/documentation/doc-5.10.1/lib/stdlib-1.19.1/doc/html/unicode_usage.html

2 Erlang で Unicode を使う

2.1 Unicode の実装

Unicode 文字セットサポートの実装は継続中のプロセスです。 Erlang 拡張提案 (EPP) 10 は Unicode サポートの基礎を概説しており、将来的にすべての Unicode 対応 モジュールが取り扱うべきバイナリの、デフォルトエンコーディングを指定しています。

EEP 10 で説明されている機能は、Erlang/OTP の R13B として実装されましたが、これは その終わりを意味するものではありません。R14B01 では Unicode ファイル名の サポートが追加されましたが、完全なものではなく、ファイル名のエンコーディングが 保証されていないプラットフォームでは、デフォルトで無効にされていました。 R16A では UTF-8 でエンコードされたソースコードをサポートし、いくつかの環境では UTF-8 エンコードされたファイルだけでなく Unicode エンコードされたファイル名を サポートし多くのアプリケーションが機能強化されました。 最も注目に値するのは file:consulr/1 によるファイル読み込みの UTF-8 サポート、 リリースハンドラの UTF-8 サポート、そして I/O システムでの更なる Unicode 文字 セットのサポートです。

R17 では Erlang ソースコードのデフォルトエンコーディングを UTF-8 に変更し、 R18 ではすべての Unicode 範囲でのアトムをサポートします。これはすべての Unicode の関数名やモジュール名を意味します。

このガイドは現在の Unicode サポートについて概説し、Unicode データを操作するための レシピをいくつか紹介します。

2.2 Unicode を理解する

Erlang における Unicode サポートの経験で、Unicode 文字とそのエンコーディングを 理解することは期待したほど簡単ではないことが痛いほど明確になりました。 フィールドの複雑さだけではなく、標準の意図も想像以上に十分な概念の理解を必要と しました。

さらに Erlang の実装は、多くの (Erlang) プログラマにとって問題であったことが ない概念を理解する必要があります。 Unicode 文字を理解し使うためには、たとえ 経験豊富なプログラマであっても、このテーマを徹底的に勉強することが必要です。

一例として、大文字を小文字に変換する問題を考えることができます。 規格を読めば、すべての回答にそもそも単なる1対1のマッピングは無いということに 気付かされるでしょう。一例としてドイツ語を取ると、小文字の "ß" (シャープ s) は 大文字の "SS" が対応します。またギリシャ語の場合、 "Σ" は2つの異なる小文字の 形式があります: 単語の末尾位置では "ς" 、他の位置では "σ" です。また、トルコ語 では ドット付きとドット無しの "i" の形式が大文字にも小文字にも存在しますし、 キリル文字の "l" は通常小文字の形式がありません。もちろん、大文字(または小文字) の概念を持たない言語もあります。そのため変換機能は一度に一文字だけではなく、 文章全体を知っている必要がありますし、恐らく自然言語の翻訳では入力と出力の 文字列長などの違いも考慮するべきでしょう。原稿執筆時点で Erlang/OTP には Unicode の to_upper/to_lower 機能はありませんが、これらの問題に対処するための 公式のライブラリがあります。

もう一つは、同じグリフが2つの異なる表現を持つアクセント記号付き文字の例です。 スウェーデン語の "ö" を見てみましょう。 Unicode 標準にはそのためのコードポイントが ありますが、 "o" に続けて U+0308 (合成用ウムラウト、簡単に言うと最後の文字の上に "¨" が付きます) を続けて書くこともできます。これらは全く同じグリフです。 これらの目的はほぼ同じですが、全く異なる表現方法しています。 例えば、 Mac OS X はすべてのファイル名に合成用ウムラウトを使いますが、一方で他の ほとんどのプログラム (Erlang を含む) は、ディレクトリ一覧の際などに逆の事をして 隠そうとします。このようなことが行われてはいますが、通常、ひどい混乱を避けるために、 そのような文字は正規化することが重要です。

例として上げた一覧は、Unicode 標準に従えば可能だ、とは思います。 ポイントは、プログラムが1つか2つくらいの言語だけを対象とした場合には考慮する 必要がなかったような知識が必要だということです。 国際的な標準の構築に挑戦したときは、人間の言葉と文章の複雑さに対して、確かに これらの考慮が必要でした。あなたのプログラムで Unicode を適切にサポートするには、 おそらく労力を必要とするでしょう。

2.3 Unicode とは何か

Unicode とは、すべての既知の、現在使われている、または使われていない文字の コードポイント (数値) を定義する規格です。原理的には、任意の言語で使われるすべて の既知のシンボルは、 Unicode コードポイントを持っています。

Unicode コードポイントは、非営利団体である Unicode コンソーシアム によって 策定、公開されています。

プログラムがグローバルな環境で使用される場合に、唯一共通の文字セットがもたらす メリットは圧倒的なものとして、コンピューティングの世界全体では Unicode の サポートが増加しています。

規格の基本: すべての文字のコードポイントに加え、使用可能な エンコーディング 規格 が複数あります。

ここで、エンコーディングと Unicode 文字の違いを理解することが不可欠です。 Unicode 文字は Unicode 規格に基づいたコードポイントですが、エンコーディングは コードポイントを表現するための方法です。エンコーディングは単に表現のための規格で、 例えば UTF-8 は非常に限定された Unicode 文字セット (例えば ISO-Latin-1)や、 Unicode のすべての範囲の文字を表現するために使用することができます。 これは単にエンコーディング形式なのです。

すべての文字セットが 256 文字に限られる限り、それぞれの文字は単一のバイトに格納 できるため、それらの文字に対して多かれ少なかれ1つは実用的なエンコーディングが ありました。それぞれの文字を1つのバイトでエンコードする方法は非常に一般的すぎて 名前すらありません。今、私たちは Unicode システムによって、256 を超える文字を 持っており、それらを表現する一般的な方法を必要としています。 コードポイントを表す一般的な方法こそが、エンコーディングです。これは文字表現が 争点のなる以前のプログラマにとって、全く新しい文字表現の概念を意味します。

異なるオペレーティング・システムやツールでは異なるエンコーディングをサポートして います。たとえば、 Linux と MacOS X は 7Bit ASCII と下位互換性がある UTF-8 エンコーディングを採用しているので、英語で書かれたプログラムに対する影響は最小限 です。一方、Windows は UTF-16 の限定バージョン、すなわち、16-bit エンティティで 格納できる文字のすべてのコード面をサポートしており、現在使われているほとんどの 言語を含んでいます。

最も広く普及しているエンコーディングは:

バイト単位表現

これは適切な Unicode 表現ではなく、Unicode 規格以前に文字を表現するために 使われていました。これはまだ Unicode 規格で 256 以下のコードポイントを持つ 文字の表現に使うことができ、これは ISO-Latin-1 文字セットと正確に対応します。 Erlang では、ISO-Latin-1 がエンコーディングではなく、文字コード範囲である かのようで紛らわしいですが、これは一般に Latin 1 エンコーディングを意味します。

UTF-8

各文字は、コードポイントに応じて1〜4バイトに格納されます。 UTF-8 で1バイトで表現可能なすべての 7-bit 文字は、7-bit ASCII 文字のバイト 単位表現と下位互換性があります。コードポイント 127 を超える文字はより多くの バイトに格納され、最初の文字の最上位ビットはマルチバイト文字を表します。 エンコードの詳細については RFC が公開されています。 UTF-8 はコードポイント 128 から 255 のバイト単位表現とは互換性がないので、 ISO-Latin-1 のバイト単位表現とは互換性が ない ことに注意してください。

UTF-16

このエンコーディングは UTF-8 に対して多くの類似点を持っていますが、基本的な 単位は 16-bit の数値です。これは少なくともすべての文字が2バイト、いくつかの 高い数字は4バイトを占有することを意味します。 一部のプログラム、ライブラリおよびオペレーティング・システムでは 16-bit の エンティティ一つに格納出来る範囲の文字のみ使うことを許可すると主張して いますが、これは通常、現在使われている言語を扱うには充分です。 基本の単位が複数バイトのため、UTF-16 にはビッグエンディアンとリトルエン ディアンの双方の形式が存在することから、バイトオーダー問題が発生します。 Erlang の場合 unicode モジュールや bit 構文の中では、UTF-16 のすべての 範囲に対応しています。

UTF-32

最も直接的な表現方法です。それぞれの文字が1つの 32-bit の数値で格納されます。 一つの文字のためにエスケープや複数の可変量のエンティティは不要で、すべての Unicode コードポイントが1つの 32-bit エンティティに格納できます。 UTF-16 の場合と同様に、UTF-32 にも ビッグエンディアンとリトルエンディアンの 両方が存在し、バイトオーダーの問題があります。

UCS-4

基本的には UTF-32 と同じですが、いくつか Unicode のセマンティクスがなく、 IEEE によって定義され、すこし異なるエンコーディング規格として使われています。 すべての普通の (そしておそらく普通でない) 用途において、UTF-32 と UCS-4 は 互換性があります。

ある特定範囲の数字、Unicode 規格の中でも未使用とされており、ある範囲は無効と 見做されています。無効な範囲で最も注目すべきなのは 16#D800 - 16#DFFF で、 UTF-16 エンコーディングではこれらの数値のエンコーディングを許可していません。 これは、当初 UTF-16 エンコーディング規格が1つの 16-bit エンティティですべての Unicode 文字を保持できると期待していたものの、下位互換性に対応するために、 Unicode の範囲に穴を残して拡張せざる得なかったためと推測できます。

また、コードポイント 16#FEFF は (BOMの) バイトオーダーマークに使われ、それ以外の 文脈で使用することは推奨されていません。実際のところ、これは "ZWNBS" (Zero Width Non Breaking Space) として有効です。BOM はエンコーディングやバイトオーダーの ような、プログラムが事前に分からないパラメータを識別するために使用されます。 バイトオーダーマークは、期待したよりもほとんどめったに使われませんが、プログラムが あるファイルの Unicode 形式について賢い推測ができる方法が提供されるにつれて、 より広く普及するかも知れません。

2.4 Unicode サポートの分野

Erlang で Unicode をサポートするために、いくつかの分野での問題が対処されています。 各分野については、このセクションでは簡単に触れ、このドキュメントの下記でより詳細に 説明します。

表現

Erlang で Unicode 文字を処理するために、リストとバイナリの両方で共通の表現を 持つ必要があります。EEP (10) と R13A 以降の最初の実装では、Erlang ので Unicode 文字の標準的な表現を解決しました。

操作

ライブラリ関数が Unicode 文字を処理できるようにする必要があるので、Unicode 文字は Erlang プログラムによって処理されるようになる必要があります。 いくつかのケースでは、機能は既存のインターフェース (例えば string モジュールは、現在任意のコードポイントでリストを扱うことが出来ます) に 追加され、またあるケースでは新しい機能やオプション (io モジュールや ファイルハンドリング、 unicode モジュールそして bit 構文など) が 追加されました。そして現在、kernel と STDLIB のほとんどのモジュールが VM と同様に Unicode に対応しています。

ファイル I/O

I/O は断然、最も Unicode 問題の解決が難しい分野です。ファイルはバイトが格納 され、またプログラミングの知識が文字とバイトに交換可能なものとして扱われる エンティティです。Unicode 文字を使うと、ファイルにデータを格納するとすぐに エンコーディングを決定する必要があります。Erlang ではエンコーディング オプションをつけてファイルを開けるので、ファイルからバイト単位ではなく、 文字を読み取ることができますが、バイト単位の I/O 用にファイルを開くことも できます。Erlang の I/O システムは任意の I/O サーバが任意の文字列データに 対処できる事を期待するように設計されて (もしくは少なくとも使われて) いますが、Unicode 文字を処理する場合それはもはや問題ではありません。 結局データのあるデバイスの機能を知る必要があるという事実を取り扱うことは、 Erlang プログラマにとって新しいものです。さらに、Erlang のポートはバイト 指向であるため、ポートに対して任意の (Unicode) 文字の文字列を初めに選択した エンコーディングに変換することなく送信することは出来ません。

ターミナル I/O

ターミナル I/O はファイル I/O よりも若干簡単です。出力は人間が読むことを 意図しており、通常は Erlang の構文です (例: シェル) 。 実際にはグリフを表示しない、任意の Unicode 文字の構文的な表現 (代わりに \x{HHH} と記述します) が存在するため、ターミナルが Unicode 範囲全体を サポートしていてもいなくても、通常は Unicode データを表示できます。

ファイル名

ファイル名は、基板となるOSやファイルシステムに応じて、異なる方法で Unicode 文字列として格納できます。これはプログラムによってかなり簡単に扱うことが できます。問題は、例えば Linux のように、ファイルシステムのエンコーディング が一致していない場合に発生します。Linux はファイルに任意のバイトシーケンスで 命名することができ、各プログラムがそれらのバイトを解釈できるように残します。 これらの "透明な" ファイル名が使われているシステムでは、Erlang は起動フラグ によってファイル名のエンコードについて知らせてもらう必要があります。 デフォルトではバイト単位の解釈で、これは普通、実際には誤りですが、すべての ファイル名の解釈が可能になります。"生のファイル名" の考え方は、それが デフォルトではないプラットフォームで Unicode ファイル名変換 (+fnu) を 有効にした場合に、間違ってエンコードされたファイル名を処理するために 使えます。

ソースコード エンコーディング

Erlang ソースコードですが、 UTF-8 エンコーディングとバイト単位の エンコーディングのサポートがあります。R16B でのデフォルトはバイト単位 (または latin1) エンコーディングです。ファイルの先頭の次のようなコメントで エンコーディングを制御できます:

%% -*- coding: utf-8 -*-

もちろん、これはあなたのエディタが同様に UTF-8 をサポートする必要があります。 同様のコメントは file:consult/1 やリリースハンドラ等のような関数にも 解釈できるので、ソースディレクトリのすべてのテキストファイルを UTF-8 で 持つことができます。

言語

UTF-8 のソースコードを持つことで、255 以上のコードポイントをもつ Unicode 文字を含む文字列リテラルを記述することも可能ですが、アトム、モジュール名、 関数名は R18 がリリースされるまでは ISO-Latin-1 の範囲に制限されます。 /utf8 を使ったバイナリリテラルも、255 以上の Unicode 文字を使って表現 することができます。7-bit ASCII 以外の文字を使用したモジュール名にすると オペレーティング・システム上でファイル命名スキームの問題を引き起こす可能性 があり、また移植性を損なってしまうかもしれないので、本当におすすめしません。 EEP 40 では、言語として、変数名に 255 以上の Unicode 文字を使えるように すべきと提案されています。その EEP を実装するか否かについては、まだ決定して いません。

2.5 標準 Unicode 表現

Elang では、文字列は実際には整数のリストです。文字列は R13 までは ISO-Latin-1 (ISO8859-1) でエンコードされるように定義されていました。 ISO-Latin-1 は Unicode 文字セットの部分コード範囲で、コードポイント単位に1対1で対応しています。

そのため、文字列のための標準のリストエンコーディングは、Unicode 範囲全体に対応 するよう容易に拡張されました: Erlang における Unicode 文字列は整数を含むシンプルな リストで、各整数は有効な Unicode コードポイントであり、Unicode 文字セット内の 一文字を表現しています。

ISO-Latin-1 エンコーディングの Erlang 文字列は、Unicode 文字列のサブセットです。

文字列に 256 未満のコードポイントのみが含まれている場合のみ、例えば erlang:iolist_to_binary/1 などを使って直接バイナリに変換したり、直接ポートに 送信することができます。文字列に 255 以上の Unicode 文字が含まれている場合は、 エンコーディングを決定しなければならず、文字列は unicode:characters_to_biary/{1,2,3} を使って望ましいエンコードでバイナリに 変換する必要があります。文字列は R13 以前にそうであったように、通常はバイトの リストではありません。それらは文字のリストです。文字は通常、バイトではなく Unicode コードポイントです。

バイナリは、もっと厄介です。パフォーマンス上の理由から、プログラムはしばしばテキ ストデータをリストではなく、バイナリで保持しますが、主な理由はよりコンパクト (リストの場合、1文字あたり1バイトではなく、文字ごとに2ワード) だからです。 erlang:list_to_bianry/1 を使うと、1文字あたり1バイトのバイト単位の エンコーディングを使って、効率的に ISO-Latin-1 の Erlang 文字列をバイナリに変換 できます。この方法はこれらの限られた Erlang 文字列には非常に便利ですが、任意の Unicode リストに対して実行できません。

UTF-8 が広く普及し、7-bit ASCII 範囲内に対する下位互換性を提供するようになるに ことで、Erlang のバイナリ内における Unicode 文字の標準エンコーディングとして 選ばれました。

標準バイナリエンコーディングは、Erlang のライブラリ関数がバイナリ内の Unicode データに対処するたびに使われますが、外部との通信時はもちろん強制ではありません。 関数やビット構文はバイナリ内の UTF-8、UTF-16 および UTF-32 をエンコードおよび デコードするために存在します。一般的に、ライブラリ関数はバイナリと Unicode に 対応しますが、標準エンコーディングにのみ対応しています。

文字データはいくつかのソースから組み合わせることができ、時には文字列とバイナリの 混合も可能です。Erlang は長らく iodataiolists という概念をもっていて バイナリとリストはバイトシーケンスを表すように組み合わせることができます。 同様に、Unicode 対応モジュールは、しばしば UTF-8 でエンコードされた文字を含む バイナリや、そのようなバイナリや Unicode コードポイントを表す数値を含むリスト などの、バイナリとリストの組み合わせが可能です:

unicode_binary() = binary() with characters encoded in UTF-8 coding standard

chardata() = charlist() | unicode_binary()

charlist() = maybe_improper_list(char() | unicode_binary() | charlist(),
                                 unicode_binary() | nil())

STDLIB の unicode モジュールも同じような、UTF-8 以外のエンコーディングを含む バイナリの混在をサポートしていますが、それは外部からと外部へのデータ変換を可能に するための特殊なケースです:

external_unicode_binary() = binary() with characters coded in
  a user specified Unicode encoding other than UTF-8 (UTF-16 or UTF-32)

external_chardata() = external_charlist() | external_unicode_binary()

external_charlist() = maybe_improper_list(char() |
                                          external_unicode_binary() |
                                          external_charlist(),
                                          external_unicode_binary() | nil())

2.6 基本言語サポート

Erlang/OTP R16 の時点で、Erlang のソースコードファイルは UTF-8 とバイト単位の エンコーディング (別名 Latin1 エンコーディング) のどちらでも記述できます。 Erlang のソースファイルのエンコーディングを明示する方法の詳細については、 epp(3) に記載されています。文字列やコメントは Unicode で書けますが、関数は まだ ISO-latin-1 文字セットの文字を使った名前でなければいけませんし、atom は 同じく ISO-latin-1 の範囲に制限されています。言語におけるこれらの制限は、 もちろんソースコードのエンコーディングには依存しません。 Erlang/OTP R18 では Unicode の名前の関数も Unicode の atom も扱うようになると 思われます。

Bit シンタックス

ビット構文には、3つの主要なエンコーディングのバイナリデータに対処するための 型が含まれています。型にはそれぞれ utf8, utf16utf32 という 名前が付いています。 utf16utf32 の型は、またビッグエンディアンと リトルエンディアンの形式があります

<<Ch/utf8,_/binary>> = Bin1,
<<Ch/utf16-little,_/binary>> = Bin2,
Bin3 = <<$H/utf32-little, $e/utf32-little, $l/utf32-little, $l/utf32-little,
$o/utf32-little>>,

便宜上、リテラル文字列は次の (または類似の) 構文を使って Unicode エンコーディング のバイナリにエンコードすることができます

Bin4 = <<"Hello"/utf16>>,

文字列 および 文字リテラル

ソースコードについては、 \OOO (バックスラッシュの後に3桁の8進数が続く) と \xHH (バックスラッシュの後に x が続き、さらに2桁の16進文字が続く) 、すなわち \x{H ...} (バックスラッシュの後に x 、続けて左波括弧、任意の16進数、右波括弧が 続く) の拡張表記があります。 これにより、ソースファイルのエンコーディングがバイト単位 (Latin-1) の場合でも、 文字列内の文字の通りに、任意のコードポイントの文字を入力できます。 シェルの場合、 Unicode の入力デバイスを使っているか、ソースファイルが UTF-8 で 保存されているなら、 $ は Unicode 文字を整数値で提供することによって、直接 追従することができます。

次の例では、出力はキリル文字の c のコードポイントです

7> $с.
1089

ヒューリスティック 文字列検出

特定の出力関数やシェル内での戻り値の出力では、Erlang はリストやバイナリデータの 中の文字列をヒューリスティックに検出しようとします。 通常、ヒューリスティックな検出というのは、このような状況で見られます

1> [97,98,99].
"abc"
2> <<97,98,99>>.
<<"abc">>
3> <<195,165,195,164,195,182>>.
<<"åäö"/utf8>>

ここでは、シェルはバイト単位または UTF-8 エンコーディングのいずれかで印刷可能な 文字を含むバイナリまたは印刷可能なを含むリストを検出します。 ここで問題です: 印刷可能な文字とは? 一つは、 Unicode 標準が印刷可能と考えているものはなんでも、ヒューリスティックな 検出に従って印刷可能となるべき、という考え方です。結果は、整数のリストのほとんど すべてが文字列とみなされ、結果としてあなたのターミナルがその文字セットを持って いないかもしれない、あらゆる種類の文字が印刷される (結果としていくつかの一般的な 出力においては、うれしくないかもしれない) ということになるでしょう。 もう一つのやりかたは、ISO-Latin-1 文字セットが文字列の検出に使えるように、下位 互換性を保つことです。3つ目の方法は Unicode 範囲が文字として表示されることを 正確にユーザに決めさせることです。 R16B では起動フラグ +pclatin1 または unicode の範囲を渡して、 Unicode 範囲全体か ISO-Latin-1 の範囲のいずれかを選択できます。下位互換性を 維持するため、デフォルト値は latin1 です。これはヒューリスティックな文字列 検出を制御するだけです。将来的に、ユーザに関連する言語や地域に対して、 ヒューリスティクスを調整できるように、より多くの範囲を追加することが期待されて います。

二つの異なる起動オプションで、例を見てみましょう

$ erl +pc latin1
Erlang R16B (erts-5.10.1) [source] [async-threads:0] [hipe] [kernel-poll:false]

Eshell V5.10.1  (abort with ^G)
1> [1024].
[1024]
2> [1070,1085,1080,1082,1086,1076].
[1070,1085,1080,1082,1086,1076]
3> [229,228,246].
"åäö"
4> <<208,174,208,189,208,184,208,186,208,190,208,180>>.
<<208,174,208,189,208,184,208,186,208,190,208,180>>
5> <<229/utf8,228/utf8,246/utf8>>.
<<"åäö"/utf8>>
$ erl +pc unicode
Erlang R16B (erts-5.10.1) [source] [async-threads:0] [hipe] [kernel-poll:false]

Eshell V5.10.1  (abort with ^G)
1> [1024].
"Ѐ"
2> [1070,1085,1080,1082,1086,1076].
"Юникод"
3> [229,228,246].
"åäö"
4> <<208,174,208,189,208,184,208,186,208,190,208,180>>.
<<"Юникод"/utf8>>
5> <<229/utf8,228/utf8,246/utf8>>.
<<"åäö"/utf8>>

例では、デフォルトの Erlang シェルが ISO-Latin-1 範囲の文字だけを印刷可能として 解釈し、それらの "印刷可能" な文字を含むリストやバイナリのみを文字列データとして 検出していることがわかります。"Юникод" を含む 有効な UTF-8 バイナリは、 文字列として出力されていません。 その一方、すべての Unicode 文字を印刷可能にして起動 (+pc unicode) した場合、 シェルは印刷可能な Unicode データを含むもの (UTF-8 かバイト単位でエンコードされた バイナリ) をすべて文字列データとして出力しています。

これらのヒューリスティクスは io(_lib):format/2 やそれに類する関数で、 t 修飾子を ~p~P と組み合わせて使う場合に、使用されます

$ erl +pc latin1
Erlang R16B (erts-5.10.1) [source] [async-threads:0] [hipe] [kernel-poll:false]

Eshell V5.10.1  (abort with ^G)
1> io:format("~tp~n",[{<<"åäö">>, <<"åäö"/utf8>>, <<208,174,208,189,208,184,208,186,208,190,208,180>>}]).
{<<"åäö">>,<<"åäö"/utf8>>,<<208,174,208,189,208,184,208,186,208,190,208,180>>}
ok
$ erl +pc unicode
Erlang R16B (erts-5.10.1) [source] [async-threads:0] [hipe] [kernel-poll:false]

Eshell V5.10.1  (abort with ^G)
1> io:format("~tp~n",[{<<"åäö">>, <<"åäö"/utf8>>, <<208,174,208,189,208,184,208,186,208,190,208,180>>}]).
{<<"åäö">>,<<"åäö"/utf8>>,<<"Юникод"/utf8>>}
ok

2.7 インタラクティブシェル

インタラクティブ Erlangシェル -- 端末、または Windows で werl コマンドを 使って実行します -- は、Unicodeの入出力をサポートします。

Windows では、適切なフォントがインストールされていて、Erlang アプリケーションが それを使用できるように適切に設定されていることが必要です。あなたのシステムで 利用可能な、適切なフォントがない場合は、DejaVu フォント (dejavu-font.org) が 自由に利用可能なので、これをインストールして、Erlang シェルアプリケーション用の フォントとしてみてください。

Unix ライクのオペレーティング・システムでは、端末は入出力の UTF-8 を扱えるはず (例えば、 XTerm, KDE konsole や Gnome terminal の最近のバージョン) なので、 あなたは、適切なロケールを設定する必要があります。例として、私の LANG 環境変数は、このように設定されています

$ echo $LANG
en_US.UTF-8

実際には、ほとんどのシステムでは LANG よりも前に LC_CTYPE 変数を扱う ので、その変数が設定されている場合は、 UTF-8 に設定する必要があります

$ echo $LC_CTYPE
en_US.UTF-8

LANG または LC_CTYPE の設定は端末でできること一致する必要がありますが、 Erlang が実際のターミナルに UTF-8 の対応状況を問い合わせるポータブルな方法はない ので、私たちは言語と文字タイプの設定に依存するしかありません。

Erlang がターミナルをどう認識しているかを調べるためには、シェルを起動した際に、 io:getopts() 呼び出しが使えます

$ LC_CTYPE=en_US.ISO-8859-1 erl
Erlang R16B (erts-5.10.1) [source] [async-threads:0] [hipe] [kernel-poll:false]

Eshell V5.10.1  (abort with ^G)
1> lists:keyfind(encoding, 1, io:getopts()).
{encoding,latin1}
2> q().
ok
$ LC_CTYPE=en_US.UTF-8 erl
Erlang R16B (erts-5.10.1) [source] [async-threads:0] [hipe] [kernel-poll:false]

Eshell V5.10.1  (abort with ^G)
1> lists:keyfind(encoding, 1, io:getopts()).
{encoding,unicode}
2>

ロケール設定、フォント、そして端末エミュレータがすべて適切に設定して初めて (ようやく?) 、スクリプトにあなたが望む文字を入力する術を手にするでしょう。 テストとして最も簡単な方法は、普段デスクトップ上の何かのアプレットを使って 行なうことに他の言語のキーボードマッピングを追加することです。 私の KDE 環境の場合、KDEコントロールセンター (個人設定) を立ち上げ、"地域と アクセシビリティ" から "キーボードレイアウト" を選択します。 Windows XP の場合は、 [コントロールパネル] -> [地域と言語オプション] を立ち上げて、 [言語] タブを選択して、"テキストサービスと入力言語" という名前の枠の中にある [詳細] ボタンをクリックします。ご使用の環境は、おそらくキーボードレイアウトを 変更する同様の手段を提供しています。 例えば、キリル文字セットを使って Erlang シェルでコマンドを入力するのは簡単では ないので、この方法を使っていない場合は、簡単にキーボードを切り替える方法がある ことを確認しましょう。

いよいよ何らかの Unicode 入出力設定ができました。もっともシンプルなやり方は、 もちろんシェル内で文字列を入力することです

$ erl
Erlang R16B (erts-5.10.1) [source] [async-threads:0] [hipe] [kernel-poll:false]

Eshell V5.10.1  (abort with ^G)
1> lists:keyfind(encoding, 1, io:getopts()).
{encoding,unicode}
2> "Юникод".
"Юникод"
3> io:format("~ts~n", [v(2)]).
Юникод
ok
4>

文字列は Unicode 文字列として入力することができますが、一方で言語要素は ISO-Latin-1 文字セットに制限されています。文字定数と文字列だけが、その範囲を 超えられます

$ erl
Erlang R16B (erts-5.10.1) [source] [async-threads:0] [hipe] [kernel-poll:false]

Eshell V5.10.1  (abort with ^G)
1> $ξ.
958
2> Юникод.
* 1: illegal character
2>

2.8 Unicode ファイル名

最近のほとんどのオペレーティング・システムでは、いくつかある方法のどれかで Unicode ファイル名をサポートしています。いくつか異なる方法がありますが、 Erlang はデフォルトで様々な異なるアプローチを扱います:

強制的 Unicode ファイルネーミング

Windows と、もっとも一般的な目的で使う MacOS X はファイル名の Unicodeサポート を強制します。ファイルシステム上で作成されたすべてのファイルが一貫して 解釈できる名前を持っています。MacOS X ではすべてのファイル名は UTF-8 エンコーディングで読み出しますが、一方で Windows は Unicode 準拠の特別な 変異を持つファイル名を扱う各システムコールに、ほぼ同様の効果を与える手法を 選択しています。 Erlang VM のデフォルトの動作は "Unicode ファイル名変換モード" -- ファイル名を Unicode リストで与えられ、それが自動的に基盤となるオペレー ティング・システムやファイルシステムにとって適切なファイル名エンコーディングに 変換されること意味します -- で動作するので、これらのファイルシステムでは Unicode でないファイル名は存在しません。

つまり、これらのシステムのいずれかで file:list_dir/1 を実行すると、実際の ファイルシステムの内容に応じて、255を超えるコードポイントの Unicode リストを 返すことがあります。

この機能はかなり新しいものなので、255より大きいコードポイントの文字を含む ファイル名を処理できないような、非コアアプリケーションで躓くかもしれませんが、 コアの Erlang システムでは Unicode ファイル名で問題はないはずです。

透過的ファイルネーミング

ほとんどの UNIX オペレーティング・システムでは、よりシンプルな手法 -- つまり Unicode ファイル名を強制するのではなく、慣例にしたがう -- を採用しています。 これらのシステムは通常、Unicode ファイル名に UTF-8 を使用しますが、それを 強制はしません。このようなシステムでは、128 から 255 の間のコードポイントを 持つ文字を含むファイル名はプレーンな ISO-latin-1 か UTF-8 エンコーディングを 使用して名前をつけることができます。整合性を強要されないので、Erlang VM は すべてのファイル名の一貫した翻訳はできません。VM が自動的に経験則に基いて エンコーディングを選択すると、これらのシステム上で予期しない動作をする でしょう。デフォルトで Erlang はそのようなファイルシステム上では "latin1" ファイル名モードで起動しますが、これはファイル名がバイト単位エンコーディング であることを意味します。 これはシステム内のすべてのファイル名のリスト表現を可能にしますが、例えば "Östersund.txt" というファイル名は、 file:list_dir/1 で "Östersund.txt" (ISO-Latin-1 のバイト単位エンコードされたファイル名でプログラムが作った場合)、 またはおそらく [195,150,115,116,101,114,115,117,110,100] のように、UTF-8 バイトを含むリストとして表示されるので、あなたが欲しいものではないかも しれません。一方でそのようなシステムで Unicode ファイル名変換を使用すると、 UTF-8 でないファイル名は file:list_dir/1 のような関数には、単純に 無視されます。そのようなファイルは file:list_dir_all/1 で取得できますが、 誤ってエンコードされたファイル名は "raw file names" と表示されます。

Unicode ファイルネーミングサポートは OTP リリース R14B01 で導入されました。 Unicode ファイル名変換モードで動作する VM は、任意の言語または文字セットの名前を 持つファイルを (それが基盤となる OS やファイルシステムによってサポートされている 限り) 処理できます。Unicode 文字リストはファイル名やディレクトリ名を表示するため に使用され、ファイルシステムの内容が表示されている場合は、戻り値としても Unicode リストを取得します。殆どのアプリケーション (ファイル名が明示的に ISO-Latin-1 の 範囲内である必要はありません) が、変更することなく Unicode サポートの恩恵を 受けられる理由は、Kernel モジュールと STDLIB モジュールにあります。

Unicode ファイル名を強制するオペレーティング・システムの場合、これは (Erlangでは ない) 他のアプリケーションのファイル名に簡単に適応させられること、また 少なくとも Windows では (ISO-Latin-1 で表現できないファイル名持つことが原因で) 全くアクセスできなかったファイルを処理できることを意味します。また、すべての ファイル名を UTF-8 として受け入れる OS の VFS レイヤーとしての MacOS X において、 理解できないファイル名を作ることを避けられ、また書き換えることもないでしょう。

ほとんどのシステムでは、透過的ファイルネーミングを使用している場合であっても、 Unicode ファイル名変換を有効にすることは問題ありません。ごく少数のシステムでは 複数のファイル名エンコーディングが混在しています。一貫して UTF-8 で命名される システムでは、Unicode ファイル名モードで完璧に動作します。しかしそのような システムは R14B01 ではまだ実験的であると考えられていて、そのようなシステムでは まだデフォルトではありません。Linux 上で Unicode ファイル名変換を +fnu スイッチと共に有効にすると、VMはファイル名変換モードがネイティブファイル名 エンコーディングである latin1 モードがデフォルトになることを示さずに起動します。 WindowsやMacOS X の場合、これらのシステム (Windowsの場合、ファイルシステムレベル では UTF-8 を使用していないという事実を、Erlangプログラマは安全に無視できます) では file:native_name_encoding/0 がデフォルトで utf8 を返すので、 デフォルトの動作は Unicode ファイル名変換のそれです。 すでに述べた通り、デフォルトの動作は +fnu または +fnl のオプションを VM に与えることによって変更できます。 erl プログラムを参照してください。 VM が Unicode ファイル名変換モードで起動した場合、 file:native_name_encoding はアトム utf8 を返します。 +fnu スイッチは、 w, i または e を付与して、誤ったエンコードの ファイル名を報告する方法を制御できます。 w は、ディレクトリ一覧で誤った エンコードをされたファイル名を "スキップ" した場合、常に error_logger へ 警告が送られること、 i はそれらの誤ったエンコードのファイル名を黙って無視する こと、 e は API 関数が誤ったエンコードのファイル (またはディレクトリ) 名に 遭遇した場合、常にエラーを返すことを意味します。 w がデフォルトです。なお、 file:read_link/1 はリンクが不正なファイル名を指し示している場合には、常に エラーを返すことに注意してください。

Unicode ファイル名モードでは、ファイル名はオプション {spawn_executable,...} と共に BIF open_port/2 に与えられ、Unicode として解釈されます。なので、 spawn_executable を使う場合、引数のオプションでパラメータリストが使えます。 引数の UTF-8 変換はバイナリを使うことで回避できます。後述の raw ファイル名に 関する説明を参照してください。

ファイルを開くときに指定されるファイルのエンコーディングオプションは、ファイル名 のエンコーディング規則とは何の関係もないということは、注目に値します。頻繁に開く ファイルの内容が UTF-8 エンコードだが、バイト単位 (latin1) エンコーディングの ファイル名をもつ、またはその逆ということができます。

Note

Erlang のドライバまたは NIF 共有オブジェクトは、まだ 127 を超えるコードポイント を含む名前を使うことができません。これは将来のリリースで削除されることが 知られている制限です。Erlang モジュールはこれができますが、間違いなく良い アイデアではありませんし、まだ実験的なものと考えられています。

Raw ファイル名に関する注意

Raw ファイル名は erts-5.8.2 (OTP R14B01) で Unicode ファイル名サポートと一緒に 導入されました。 "Raw ファイル名" がシステムに導入されたのは、同じシステム上で 異なるエンコーディングを指定されたファイル名を一貫して表現できるようにするため でした。UTF-8 でないファイル名を Unicode 文字のリストに自動的に変換する VM を 持つことは実用的に見えるかもしれませんが、これは名前の重複や、その他の一貫性を 欠いた振る舞いをしやすくなります。Erlang VM が Unicode ファイル名モードで ( したがって UTF-8 でファイル命名されることを期待して) 動作しているとして、 ISO-Latin-1 の "björn" という名前をもつファイルを含むディレクトリを考えて みましょう。ISO-Latin-1 の名前は有効な UTF-8 ではなく、例えば file:list_dir/1 で、自動変換を考慮しようとされるのことはいいアイデアです。しかし、ファイルを 開いて、(魔法のように ISO-Latin-1 のファイル名から変換された) Unicode リストの 名前をつけたら何が起こるでしょうか? VM は、これは期待するコーディングなので、 与えられたファイル名を UTF-8 に変換します。事実上、これは <<"björn"/utf8>> と いう名前のファイルを開こうとしていることを意味します。このファイルは存在しません し、もし存在したとしても一覧に表示されたものと同じファイルではないでしょう。 "björn" という名前のひとつは UTF-8 エンコーディングで命名し、他方はそうではない ファイルを2つ作ることもできます。もし file:list_dir/1 が ISO-Latin-1 の ファイル名を自動的にリストに変換すれば、結果として2つの同一のファイル名を得る ことになります。これを避けるため、Unicode ファイル命名規則にしたがって適切に エンコードされたファイル名 (つまり、UTF-8) と、そのエンコードの元では無効な ファイル名を区別する必要があります。一般的な file:list_dir/1 関数の場合、 誤ってエンコードされたファイル名は Unicode ファイル名変換モードでは単に 無視されますが、 file:list_dir_all/1 の場合、無効なエンコーディングの ファイル名は "生" のファイル名、すなわちバイナリとして返されます。

Erlang の file モジュールは、入力として "生" のファイル名を受け付けます。 open_port({spawn_executable,...} ...) も受け付けます。先に述べたとおり、 open_port({spawn_executable,...} ...) にオプションリストで与えられた引数は、 ファイル名と同様の変換を受けるので、実行可能ファイルも同様に、引数と共に UTF-8 で 提供されることを意味します。 この変換は引数をバイナリで与えることによって、ファイル名がどう扱われるかを 一貫して回避することができます。

Unicode ファイル名変換モードをそれがデフォルトでないシステムで強制することは、 初期の実装では、誤ったエンコードのファイル名を無視しなかったという事実から、 OTP R14B01 では実験的と考えられていました。そのため、Raw ファイル名がシステム 全体に予期せず拡大する可能性があります。R16B 以降、誤ったエンコードのファイル名は 特別な関数 (たとえば file:list_dir_all/1) によってのみ取得され、また "Raw" ファイル名がサポートされるようになったので、既存のコードへの影響ははるかに 少ないです。 Unicode ファイル名変換は、将来のリリースではデフォルトになると予想されます。

もしあなたが VM による自動的な Unicode ファイル名変換を使っていないとしても、 UTF-8 としてエンコードされた Raw ファイル名を用いて、UTF-8 エンコーディングの 名前を持つファイルのアクセスや作成ができます。Erlang VM が起動しているであろう モードに関係なく UTF-8 エンコーディングを強制することは UTF-8 のファイル名を 使う規則が広がっているのと同様に、いくつかの状況においては良いアイデアです。

MacOS X に関する注意

MacOS X の VFS レイヤーは非常に積極的な方法で UTF-8 ファイル名を強制します。 古いバージョンでは、単純に UTF-8 に準拠しないファイル名の作成を拒否することで これを行っていましたが、より新しいバージョンでは問題のバイトを "%HH" シーケンス (HH はオリジナルの文字の16進数表現です) で置き換えます。Unicode 変換は MacOS X ではデフォルトで有効になっているので、これが問題になるのは VM を +fnl フラグ をつけて起動するか、バイト単位 (latin1) エンコーディングのファイル名を使う場合 だけです。127 から 255 の間のコードポイントの文字を含むバイト単位エンコーディング のRawファイル名を使ってファイルを作成すると、そのファイルはファイルを作成した ときと同じ名前で開くことはできません。ファイル名を正しいエンコーディングに保つ 以外に、この動作に対する救済策はありません。

MacOS X はファイルの名前を再編成もするので、アクセント等の表現は "結合文字" を 使います。すなわち、文字 ö はコードポイント [111,776] (111 は 文字 o 、776 は 特別なアクセント文字 "結合用ウムラウト"です) として表現されます。 このユニコード正規化の方法はめったに使われないのと、Erlang は検索時にこれと 反対の方法でそれらのファイル名を正規化するので結合用アクセントを使用する ファイル名は Erlang アプリケーションに渡されません。Erlang はファイル名 "björn" を、ファイルシステムが異なるものと認識するかもしれないにも関わらず、[98,106,117, 776,114,110] ではなく [98,106,246,114,110] として取得します。実際にファイルに アクセスする際には、アクセントを結合する正規化がやり直されるので、通常 Erlang プログラマはこれを無視することができます。

2.9 環境およびパラメータの Unicode

環境変数とその解釈は、ファイル名とほとんど同じ方法で扱われます。Unicode ファイル名 が有効になっている場合、Erlang VM へのパラメータと同様に環境変数は Unicode である ことが期待されます。

Unicode ファイル名が有効になっている場合、 os:getenv/0os:getenv/1 および os:putenv/2 の呼び出しは Unicode 文字列を処理します。UNIX ライクな プラットフォームでは、組み込み関数は環境変数を UTF-8 から Unicode 文字列へ (またはその逆も) 、255 より大きいコードポイントを含む場合でも可能な限り変換 します。Windows では、環境システム API の Unicode バージョンが使用され、また 255 より大きいコードポイントも可能です。

UNIX ライクなオペレーティング・システムでは、Unicode ファイル名が有効になっている 場合、パラメータは変換なしの UTF-8 であることが期待されます。

2.10 Unicode 対応モジュール

ほとんどの Erlang/OTP モジュールは、Unicode の概念を持っていませんし、実際に 持つ必要がないという意味で、もちろん Unicode には非対応です。一般的にこれらは 非テキストまたは (gen_tcpのような) バイト指向データを扱います。

実際にテキストデータを扱うモジュール (io_lib や string 等) は、ときどき Unicode 文字列を扱えるようにする変換や拡張の対象になります。

幸いなことに、ほとんどのテキストデータはリストに格納されており、範囲のチェックも わずかなので、string のようなモジュールはちょっとした変換や拡張を必要とするだけで 十分に機能します。

しかし、一部のモジュールは明示的に Unicode に対応するように変更されています。 これらのモジュールが含まれます :

unicode

unicode モジュールは明らかに Unicode 対応です。バイトオーダーマーク (BOM) を識別するための幾つかのユーティリティと同様に、異なる Unicode 間での変換を 行う関数を含みます。Unicode を扱う一部のプログラムはこのモジュールなしでも 生き残るでしょう。

io

io モジュールは Unicode データを処理するために、実際の I/Oプロトコルと ともに拡張されています。これは、いくつかの関数はバイナリが UTF-8 であることと Unicode 文字列を出力可能にする制御シーケンスがあることを必要とすることを 意味します。

file, group, user

システム全体のI/OサーバはUnicodeを処理することができますし、デバイスへ/からの 実際の出力または入力時にデータ変換するためのオプションを持っています。 先に示したように、 shell はUnicodeの端末をサポートしており、また file モジュールはディスク上のさまざまなUnicodeフォーマットから/へ変換できます。

しかし、Unicodeデータをもつファイルの実際の読み書きをインターフェイスがバイト 指向の file モジュールで行うのは最適ではありません。(UTF-8などの)Unicode エンコーディングで開かれているファイルは、 io モジュールを使って読み書き するのが最適です。

re

re モジュールは特別なオプションとしてUnicode文字列のマッチングを可能に しています。ライブラリは実際にはバイナリでのマッチングが中心ですが、Unicode サポートはUTF-8が中心です。

wx

wx グラフィカルライブラリはUnicodeテキストを幅広くサポートしています。

string モジュールは ISO-Latin-1 文字セットに対してのみ正しく動作するような、 言語に依存する to_upperto_lower 関数を除いて、ISO-Latin-1文字列と同じ ようにUnicode文字列に対しても完璧に機能します。これらは大文字と小文字の変換を行う 時に、言語とロケールの問題と同様に複数文字のマッピングを考慮する必要があるため、 現在の形式では実際にUnicode文字列に対して正しく動作しません。インターナショナルな 環境での大文字・小文字の変換は、まだOTPで扱われていない大きな課題です。

2.11 ファイル内の Unicode データ

2.12 オプションのまとめ

The LANG and LC_CTYPE environment variables

The +pc { unicode | latin1 } flag to erl(1)

The +fn {l | a | u } [{ w | i | e }] flag to erl(1)

epp:default_encoding/0

io:setopts/{1,2} and the -oldshell/-noshell flags.

2.13 レシピ

Byte Order Marks

Formatted I/O

Hueristic Identification of UTF-8

Lists of UTF-8 Bytes

Double UTF-8 Encoding

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment