memoranda2015-09-25Haskellのパース処理(文字コードについて)文字コードについてUnicodeは文字集合である。Shift-JISやEUC-JPは別の文字集合を扱う)。Unicodeでは文字集合を、コードポイントという各文字に対する通し番号のようなもので管理する。コードポイントは符号化方式を意識しておらず、すなわち符号化されたバイト列ではない(バイト列は符号化方式によって決まる)。UTF-16は、文字を16bit単位で表すための符号化方式で、ASCII非互換である。最小単位が2byteであるため、ビッグエンディアンか、リトルエンディアンか、で符号化されたバイト列の解釈が変わってしまうため、エンディアンを示すBOM(バイトオーダーマーク)をテキストデータの先頭に付加する。UTF-16BE・UTF-16LEという符号化方式では、符号化方式自体でそれを示しており、BOMは付加されない。HaskellではChar型の値を内部的にUTF-16として扱うUnicodeのコードポイントとして保持する*1。UTF-16では、1符号(2byte)で文字を表す場合、Unicodeのコードポイントをそのまま使う。U+DFFFの範囲)をペアで使うため、サロゲートペアと呼ばれる。Haskellのprintの出力はUnicodeのコードポイントを10進数で出力する。putStr|putStrLnは、端末のロケールに関する環境変数を参照して、文字コードをUnicodeのコードポイントから変換して表示するので、その環境の文字コードがUnicodeを扱う符号化方式であれば、端末上で正しく表示できる。UTF-8は、文字を8bit単位で表すための可変長の符号化方式で、ASCII互換である*2。先頭ビットが0の場合は8bitx1、110の場合は8bitx2、1110の場合は8bitx3、11110の場合は8bitx4、という長さで1文字を表す。下段のリンクにあるように、Windowsアプリで文字コードとして"Unicode"が選択肢にあるのが混乱の一端。Text.Regex.Posixについて正規表現を扱うためのモジュール。=~が定義されており、自由に型を指定することで、マッチング有無(Bool)やマッチした文字列(String)、マッチした回数(Int)を取り出すことができる。UTF-8の全角スペース(0xE8,0x80,0x80)が含まれる場合、マッチングがうまくいかなくなる*3。invalid"))Text.Parsecについて文字列のパース処理についてはText.Parsecが使いやすい。使い方についてはUnbui.ltが分かり易い。モナドのdo記法をDesugarした考察が載っていて親切。stringParsecを使い始めた当初、単語を取り出すパーサを書いてみたが、実行時に上記のエラーとなった。""を受理してしまっていたからだと思われる。Parsec.manyは0以上のパターンにマッチするので、パターンとして空文字列を渡してしまうと、パターンマッチが永久に終わらない。</text>からpropertyを意識せずにコンテンツを取り出したい場合、以下のex1.hsのbadparserのように書けそう*5だが、これはうまくいかない。ahead)をせず、一つずつマッチング対象の文字を調べていく。これをconsumeと呼び、マッチングに成功した文字を二度と使わない、という点がうまく表現されている。textタグが現れるよりも前に他のタグ<page>などが存在した場合、<は<textの先頭にマッチするものの、その次のpはtextの先頭にはマッチしないのでエラーとなり、そこでマッチングが終了してしまう。そこで、Parsec.tryを使うと、consumeスタイルのマッチングにエラーが発生した場合でも、改めてマッチングをやり直してくれるため、ex1.hsのgoodparserのようにうまくいく*6。<b>conntent.</b>ByteStringとData.TextについてString型は、Char型のリストであるためメモリ効率が悪く、文字列操作の性能は高くない*7。そのため、Unicodeを扱う場合、IOからメモリ効率の良いByteString型として読み込み*8、それを使いやすいData.Text型に変換して使用するのが良いようだ*9。ByteStringを使ってバイト単位から文字コードを生成することもできる。ByteStringとして読み込んだUnicodeのバイト列をString型に変換(してメモリ上に配置)するにはutf8-stringパッケージを利用する。GitHubLanguageプラグマについてプラグマはGHCの拡張機能で、Languageプラグマは言語に関する機能を拡張する。#-}を付けることによって文字リテラルをString型以外の型として扱うことができる。以下のコードは、Languageプラグマを付けないとコンパイルエラーとなる。ByteString型は1文字を1バイトで格納するため、UTF-8で符号化された*101文字3バイトの文字コードを格納しきれず、1バイトに切り詰められていることが分かる。Python2とPython3の文字型の扱いについてPython2系とPython3系では文字型の扱いが異なっている。文字型は、エンコーディングされたバイト列を保持する。uフラグ*11をつけたリテラルは、ユニコード文字型として、Unicodeのコードポイントを保持する。文字型は、Unicodeのコードポイントを保持する、ただし、bフラグをつけたリテラルは、バイト型*12として0<=x<256の間の整数値のシーケンスを保持する。256以上のコードポイントが割り当てられている文字(ASCIIで扱える文字以外)は、バイト型として扱えないことに注意。Python3では文字列は全て内部的にUnicodeのコードポイントとして保持される。そのため、Unicodeの文字集合を扱わない符号化方式で符号化されたファイルを読み込む場合は、encodingを指定してUnicodeのコードポイントに変換する必要がある。プログラミング入門*2:UTF-8が扱える範囲は8bitx6までだが、実際に使われているのは8bitx4まで。問題はそれだけではなさそうだが。*4:しかし、当時のコードはどこかへいってしまい再現する方法がない。それにしても、うまく「文字数字シングルクオーテーションハイフン以外」をセパレータのパターンとして指定する方法が分からない...*5:do式では、最後の式の結果がdo式の値となるので、それ以外の式の結果は単に捨てられている。*6:Parsecの場合はコンテンツ部分に全角スペースが存在していてもマッチングに問題は無かった。Text.Regex.PosixがUnicodeに非対応なだけ?*7:大きなサイズのファイルの内容を読み込む際、String型はhGetLineを使って手軽に行ごとに読み込める利点はある。*8:ByteStringはWord8と呼ばれる1バイトの単位で管理される。*9:なお、lengthの計算時間は、StringではO(N)、ByteStringはO(1)らしい。こちらの記事のオマージュです。仮想マシンのスタックとネットワークI/Oについて月別アーカイブ参加グループインターネットテクノロジープログラミングはてなブログをはじめよう!nishidyさんは、はてなブログを使っています。ブログを報告するスターをつけました引用をストックしましたストック一覧を見る閉じる引用するにはまずログインしてくださいログイン閉じる引用をストックできませんでした。再度お試しください閉じる限定公開記事のため引用できません。