Haskell の文字列に含まれる全角英数字を半角に、半角英数字を全角に変換する。

入力された文字列の英数字を全角から半角に変換したい場合があります。
Haskell の内部コードは UCS4 が使用されています。UCS4 では ASCII の文字は1バイト文字列と同じ順序で領域が割り当てられています。それに対応する全角文字は 0xff01 から割り当てられています。
(SPACE は ASCII:0x20 JIS X 0208:0x3000)

-- ASCII
hanSpaceChar = chr 0x20
han = map chr [0x21..0x7e]        -- !"#$%・・略・xyz{|}~
-- ASCIIの全角に該当する文字
zenSpaceChar = chr 0x3000
zen   = map chr [0xff01..0xff5e]  -- !釤#$%・・略・xyz{|}〜

並んでいる順序が同じですから、その差 0xfee0 を足したり引いたりすれば簡単に全角、半角を変換できるのです。

import Data.Char
import Numeric

han = map chr [0x21..0x7e]

zen   = map chr [0xff01..0xff5e]

zenSpace = ' '

toZenChar :: Char -> Char
toZenChar c = chr (ord c + 0xfee0)

toHanChar :: Char -> Char
toHanChar c = chr (ord c - 0xfee0)

zenToHan :: String -> String
zenToHan []     = []
zenToHan (c:cs) = if ord c >= 0xff01 && ord c <= 0xff5e 
                      then toHanChar c : zenToHan cs
                      else c:zenToHan cs

zenSpToHanSp :: String -> String
zenSpToHanSp []     = []
zenSpToHanSp (c:cs) = if c == zenSpace
                      then ' ' : zenSpToHanSp cs
                      else c:zenToHan cs

hanToZen :: String -> String
hanToZen []     = []
hanToZen (c:cs) = if ord c >= 0x21 && ord c <= 0x7e 
                      then toZenChar c : hanToZen cs
                      else c:hanToZen cs

hanSpToZenSp :: String -> String
hanSpToZenSp []     = []
hanSpToZenSp (c:cs) = if c == ' '
                      then zenSpace : hanSpToZenSp cs
                      else c : hanSpToZenSp cs

main = do
  -- 半角の隣に対応する全角を表示してみる。
  -- 同じ順序で割り当てられているのが確認できる。
  putStrLn $ concat $ map (\(han,zen)->[han,zen]) $ zip han zen
  -- => !!"釤##$$%%&&'釻(())**++,,-−..
  -- //00112233445566778899::;;<<==>>??
  -- @@AABBCCDDEEFFGGHHIIJJKKLLMMNNOOPPQQRRSSTTUUVVWWXXYYZZ
  -- [[\\]]^^__``aabbccddeeffgghhiijjkkllmmnnooppqqrrssttuu
  -- vvwwxxyyzz{{||}}~〜

  -- 半角を全角に変換して表示
  putStrLn $ hanToZen han
  -- => !釤#$%&釻()*+,−./0123456789:;<=>?
  --    @ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]
  -- ^_`abcdefghijklmnopqrstuvwxyz{|}〜

  -- 全角を半角に変換して表示
  putStrLn $ zenToHan zen
  -- => !"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]
  --    ^_`abcdefghijklmnopqrstuvwxyz{|}~

半角カナ、全角カナの変換も同様にやろうとしたが同じ順序に並んでいなかった・・・Orz