HaskellでUTF8エンコーディング

ビット演算の練習もかねて、ghciでputStrLnで日本語文字が出せないのもつらいので書いてみた。ghcのCharはunicode(16bit)だけど、putChar等がバイト列でのみしか扱えないので。

import Data.Char
import Data.Bits

utf8 :: String -> String
utf8 s = concatMap toUTF8 (toUCS4 s)

toUCS4 :: String -> [Int]
toUCS4 [] = []
toUCS4 (a:b:t) | 0xd800 <= av && av <= 0xdbff &&
                 0xdc00 <= bv && bv <= 0xdfff = unpair:(toUCS4 t)
                     where
                       av = ord a
                       bv = ord b
                       unpair = ((shiftL (av .&. (bits 10)) 10) .|.
                                 (bv .&. (bits 10))) + 0x10000
toUCS4 (a:t) = (ord a):(toUCS4 t)

toUTF8 :: Int -> String
toUTF8 chv = if chv < (bit 7) then [chr chv]
             else if chv < (bit 11) then [h 1, t 1]
             else if chv < (bit 16) then [h 2, t 2, t 1]
             else if chv < (bit 21) then [h 3, t 3, t 2, t 1]
             else if chv < (bit 26) then [h 4, t 4, t 3, t 2, t 1]
             else if chv < (bit 31) then [h 5, t 5, t 4, t 3, t 2, t 1]
             else undefined
    where
      t n = chr $ shiftR (chv .&. (bits (6 * n))) (6 * (n - 1)) .|. bit 7
      h n = chr $ shiftR (chv .&. mask) shifts .|.  head (7 - n)
          where
            shifts = 6 * n
            mask = bits (shifts + (6 - n))
            head 7 = bit 7
            head n = head (n + 1) .|. bit n

bits 0 = 0
bits n = bit (n - 1) .|. bits (n - 1)

で、

$ ghci utf8.hs
GHCi, version 6.8.2: http://www.haskell.org/ghc/  :? for help
Loading package base ... linking ... done.
[1 of 1] Compiling Main             ( utf8.hs, interpreted )
Ok, modules loaded: Main.
*Main> putStrLn $ utf8 "こんにちは"
こんにちは
*Main> 

みたいな感じ。サロゲートペア変換入れました。変な感じ。