Windows版HaskellとCygwinの文字コード

Windows版HaskellをCygwinから使っていて文字コードでトラブったので、その備忘録です。

きっかけは

main = putStrLn "ℕ"

a.hs: <stdout>: commitBuffer: invalid argument (invalid character)

というエラーを吐いたことです。"ℕ"じゃなくて"あ"なら出るんです。

ここで「なんだ、Haskellはユニコードをまともに扱えないのか」と思ってしまいました。ユニコード使いまくりの自作言語を作ってみたかったので「じゃあRubyでやるか」となったのですが、Raccでもℕが期待通りに動かなかったので、もう一度Haskellで試してみることにしました。

unicode-showをcabalから入れて、uprintを試すも、特に変化なし。

{-# LANGUAGE UnicodeSyntax #-}を付けてみるも変化なし。

setLocaleEncoding utf8を試すとエラーは出なくなるけど文字化けする。

ここで

import GHC.IO.Encoding

main = do
  getLocaleEncoding >>= print

の結果がCP932でした!!!

「えっ?なんで?だってCygwinのターミナルはutf-8のはずでは?だったら"あ"とかも出ないはずでは?」と思いぐぐってみると20190222: Cygwin - ターミナルエミュレータと文字化けというページがありました。

どうやら、conhostというのが勝手に変換しているようなのです!そして、パイプでつなぐと最後のコマンドの文字コードが変換の根拠になるもよう。ということで、

utf-8.hs
import GHC.IO.Encoding

main = do
  setLocaleEncoding utf8
  getLocaleEncoding >>= print
  putStrLn "あいうえおℕ"

runghc utf-8.hs | catで実行したら正常に出た!!!

どうやら、最初の例でエラーになったのはcp932にℕが無いからのもよう。単純にutf-8.hsを実行するとconhostがプログラム中でエンコーディングを変更したのを認識せずにcp932だと思ってutf-8に変換するからのもよう。| catを付けるとCygwinのコマンドが最後に実行されたのでutf-8だと判断して素通しにするもよう。全部推測でしかありませんが…

これはCygwinがやっているのかとも思いましたが、cmdからWSLを使って日本語文字列をパイプで扱うとcmdのコードページが壊れるというページがあって、Cygwinと関係なく起こる事象のようです。どうやら、WSLの導入にあたってWSL側の文字コードとcmd側の文字コードの違いに悩まなくていいように自動変換が導入されたみたいですね。勝手にやるなよ…

Haskellのプログラム中でsetConsoleOutputCP 65001の指定でも正常に動きました。setLocaleEncodingはプログラム中で使うエンコードの変更で、SetConsoleOutputが出力するエンコードの指定なのかと思ったけど、どうやら後者はchcp 65001と同じ動作のようです。でも、なんでそれで動くのかよくわからない。

ちなみにCygwinからchcpを使うときはchcp.com 65001みたいに.comを付けて使うようです。DOSからだと.exe.comは補ってくれるけど、Cygwinからは.exeだけということでしょうか?

Haskellでimport System.Win32.Consoleとかはhidden packageだから明示的に指定しないと使わせないというエラーを出すんですね。そこまでわかっているなら使わせてくれても良さそうだけど…

b.hs:5:1: error:
    Could not load module eSystem.Win32.Consolef
    It is a member of the hidden package eWin32-2.6.2.1f.
    You can run e:set -package Win32f to expose it.
    (Note: this unloads all the modules in the current scope.)
    Use -v (or :set -v in ghci) to see a list of the files searched for

そして、runghc -package Win32とやってもエラーになる。どうやらrunghc --ghc-arg=-package=Win32 b.hsとやらなければならないもよう。なんでこうなっているのだろう?

ぐぐって調べている途中で見つけたのですが、HaskellとPythonはユニコードのコードポイントを内部表現で使っていて、Javaとかは特定のエンコーディングが指定されているみたいですね。Rubyは変態でCSIというコードの情報を持つバイト列みたいです。ユニコードのコードポイントを使う方式もUCSの一つということになるのでしょうか?

Windowsのデフォルト文字コードをutf-8に変更することもできるみたいです。というか、まだなっていなかったのか。でも、なにがあるかわからないからこのままでいいか。

コメント

このブログの人気の投稿

五十音配列付き新下駄配列

WSLでの親指シフトはどうやらMozcで実現可能と気がつくまで

親指シフト新下駄配列の可能性