ログ日記

作業ログと日記とメモ

Haskellでの文字コードの扱い方が分かってきた

encodeString は Haskellの内部エンコーディングの文字列を UTF-8 に変換する。
decodeString は UTF-8 の文字列をHaskellの内部エンコーディングに変換する。
http://blog.kfish.org/2007/10/survey-haskell-unicode-support.html


しかしUTF-8コマンドプロンプト上に日本語を表示するためにはputStrLnを使うだけでいい。

Prelude> putStrLn "あいうえお"
あいうえお

これが混乱の元な気がする。これはシェルの環境を引き継いでいる。

% echo $LANG
ja_JP.UTF-8

LANGをリセットしてやってみる。

% export LANG=C 
putStrLn "
<stdin>: hWaitForInput: invalid argument (Invalid or incomplete multibyte or wide character)

入力途中に怒られてしまった。
hSetEncodingを使うらしいけれど、ハンドルごとに引数を取るのがちょっとアレ。
環境変数に左右されるぐらいなら、ByteStringからunpackでStringに変換した方が良さそう。



ネットワークやCGIなどのモジュールはByteStringを扱うものが多いが、pack、unpackを使う場合はソース中の日本語文字が壊れる。
Haskellの内部エンコーディング文字列は扱えないようだ。上記動作のおかげで内部の文字列もUTF-8として扱えているものだと錯覚していた。

Prelude Data.ByteString.Lazy.Char8> Prelude.putStrLn $ unpack $ pack "あいうえお"
BDFHJ

Prelude Data.ByteString.Lazy.Char8> Data.ByteString.Lazy.Char8.putStrLn $ pack "あいうえお"
BDFHJ
Prelude Data.ByteString.Lazy.Char8 Codec.Binary.UTF8.String> Prelude.putStrLn $ decodeString $ unpack $ pack $ encodeString "あいうえお"
あいうえお


フォームやファイルなど外から来る日本語の文字列は decodeStringを使うとHaskell内部エンコーディングの文字列になる。
packに渡す文字列をソースコード内に記述した場合は encodeString を使ってUTF-8文字列にする必要がある。


CGIのoutput関数やこの前のメール送信用のコードは pack を使っている。
なのでソースコード内の日本語文字列は出力前にencodeStringで変換する必要があるようだ。



ファイルを読み込んでそのままCGIで表示する場合は encodeString は要らないが、ソース中の文字列と連結するなどができない。

-- templateString は Data.ByteString.Lazy.Char8.readFile で読み込んで unpack した文字列だとして…

-- 結果をUTF-8で。
-- packに渡せる。出力用。
ret1 = templateString ++ (encodeString "日本語")

-- 結果を内部エンコーディングで。
-- 文字の計算ができる。ソース内の日本語と連結とかlengthで数えるとか。
ret2 = decodeString templateString ++ "日本語"

何がややこしいって、両方ともStringなので段々訳が分からなくなってくるところ。折角の型システムがあるのだから型を変えてほしい…。




# その他メモ


サーバで設定してしまうのは考え物だが、ApacheFastCGIを使っているなら

FastCgiConfig -initial-env LANG=ja_JP.UTF-8

を設定することでSystem.IO.readFileがUTF-8のファイルを読めるようになった。


UTF-8からJISへ変換する場合を詳しく書いてみた。

% cat convert.hs
import qualified Data.ByteString.Lazy.Char8 as BS
import Codec.Text.IConv (convert)
import Codec.Binary.UTF8.String (encodeString)

main = do
  innerStr <- getContents -- UTF-8文字列を読み込むためには環境変数設定が必要。自動でdecodeStringされているようなもの?
  let utf8Str     = encodeString innerStr
      utf8ByteStr = BS.pack utf8Str
      jisByteStr  = convert "UTF-8" "ISO-2022-JP" utf8ByteStr
      jisStr      = BS.unpack jisByteStr
  putStrLn jisStr

% echo $LANG
ja_JP.UTF-8

% ./convert > jis.txt
ああ

新規作成された jis.txt を開くと文字コードがちゃんと変換されていることが分かる。しかし再び上書きしてみるとUTF-8になっていた。
System.IOのputStrLnじゃダメな気がするが、エラーは出ない。うーむ。