参考:Newtype - HaskellWiki
The Haskell 98 Report: Declarations
| data | type | newtype | |
|---|---|---|---|
| 一言で言うと | 新規型定義 | シノニム | 独立型定義 |
| 既存型との区別 | - | 区別されない(混在可) | 区別される(混在不可) |
| データコンストラクタ数 | 1以上 | - | 1 |
| フィールド数 | 0以上 | - | 1 |
| data | type | newtype | |
|---|---|---|---|
| 一言で言うと | 新規型定義 | シノニム | 独立型定義 |
| 既存型との区別 | - | 区別されない(混在可) | 区別される(混在不可) |
| データコンストラクタ数 | 1以上 | - | 1 |
| フィールド数 | 0以上 | - | 1 |
import Data.Char (GeneralCategory(..), generalCategory)
main = do
let c = '\x20213'
putChar c
putStrLn $ show c
putStrLn $ show $ generalCategory c
putStrLn $ if generalCategory c == OtherLetter then "yes" else "no"
putStrLn $ if generalCategory c == Surrogate then "yes" else "no"Mac OS X 10.6.8のターミナルからリモートログインして実行したときの結果。ターミナルではCourierフォントを使っていましたが文字化けせずに表示されました。
*Main> main 𠈓'\131603' OtherLetter yes no
予想外だったのはgeneralCategoryの結果がSurrogateではなくOtherLetterだったこと。調べてみるとこれは単に私がUnicodeをよく知らないだけでした(以下の調査結果参照)。
ghc-7.0.3-src.tar.bz2のlibraries/Base/Data/Char.hsを見てみたところ、以下のような定義になっていた。
-- | The Unicode general category of the character. generalCategory :: Char -> GeneralCategory #if defined(__GLASGOW_HASKELL__) || defined(__NHC__) generalCategory c = toEnum $ fromIntegral $ wgencat $ fromIntegral $ ord c #endif #ifdef __HUGS__ generalCategory c = toEnum (primUniGenCat c) #endifwgencatは79行目に定義されていました。
#ifdef __NHC__ import Prelude import Prelude(Char,String) import Char import Ix import NHC.FFI (CInt) foreign import ccall unsafe "WCsubst.h u_gencat" wgencat :: CInt -> CInt #endif
u_gencatはbase/cbits/WCsubst.cで定義されていました。
int u_gencat(int c)
{
return getrule(allchars,NUM_BLOCKS,c)->catnumber;
}getruleの定義はこちら。どうやらallcharsという配列をバイナリサーチで探すようだ。
static const struct _convrule_ *getrule(
const struct _charblock_ *blocks,
int numblocks,
int unichar)
{
struct _charblock_ key={unichar,1,(void *)0};
struct _charblock_ *cb=bsearch(&key,blocks,numblocks,sizeof(key),blkcmp);
if(cb==(void *)0) return &nullrule;
return cb->rule;
}目で探してみるとこちら。
{131072, 42711, &rule45},
{194560, 542, &rule45},rule45を見てみるとLO、つまりOtherLetterです。
static const struct _convrule_ rule45={GENCAT_LO, NUMCAT_LO, 0, 0, 0, 0};一方、Surrogateを探すとこちらにありました。
static const struct _convrule_ rule157={GENCAT_CS, NUMCAT_CS, 0, 0, 0, 0};使っている箇所は以下の3箇所でした。
{55296, 896, &rule157},
{56192, 128, &rule157},
{56320, 1024, &rule157},
{57344, 6400, &rule158},http://www.unicode.org/Public/zipped/6.0.0/UCD.zipのUnicodeData.txtを見て謎が解決しました。General CategoryがCs (Surrogate)なのはサロゲートペアの片割れのコードということのようです。
…(略)… D800;;Cs;0;L;;;;;N;;;;; DB7F; ;Cs;0;L;;;;;N;;;;; DB80; ;Cs;0;L;;;;;N;;;;; DBFF; ;Cs;0;L;;;;;N;;;;; DC00; ;Cs;0;L;;;;;N;;;;; DFFF; ;Cs;0;L;;;;;N;;;;; …(略)… 20000; ;Lo;0;L;;;;;N;;;;; 2A6D6; ;Lo;0;L;;;;;N;;;;; …(略)…
$ cabal install time
import Data.Time
import System.Locale
main = do
t <- getCurrentTime
putStrLn $ "day=" ++ (show $ toGregorian $ utctDay t)
putStrLn $ "time=" ++ (show $ utctDayTime t)
tz <- getTimeZone t
putStrLn $ "timezone=" ++ show tz
lzt <- utcToLocalZonedTime t
let lt = zonedTimeToLocalTime lzt
ltod = localTimeOfDay lt
putStrLn $ "localTimeOfDay=" ++ show ltod
putStrLn $ "localTimeOfDay hour=" ++ (show $ todHour ltod)
putStrLn $ "localTimeOfDay min=" ++ (show $ todMin ltod)
putStrLn $ "localTimeOfDay sec=" ++ (show $ todSec ltod)
let fmt = iso8601DateFormat $ Just "%T"
putStrLn $ "formattedUTCTime=" ++ (formatTime defaultTimeLocale fmt t)
putStrLn $ "formattedLocalTime=" ++ (formatTime defaultTimeLocale fmt lzt)
実行結果
day=(2011,7,18) time=58373.107045s timezone=JST localTimeOfDay=01:12:53.107045 localTimeOfDay hour=1 localTimeOfDay min=12 localTimeOfDay sec=53.107045000000 formattedUTCTime=2011-07-18T16:12:53 formattedLocalTime=2011-07-19T01:12:53
$ sudo yum install libicu-devel $ cabal install text-icu
import Data.Text.ICU.Convert as C
import Data.ByteString as B
main = do
str <- B.readFile "hello_sjis.txt"
cp932 <- C.open "cp932" (Just False)
utf8 <- C.open "utf8" (Just False)
B.putStr (C.fromUnicode utf8 (C.toUnicode cp932 str))
toUnicodeの戻り値の型はData.TextではなくData.Text.Internal.Textです。で、表示の仕方がよくわからないので、UTF8のByteStringに変換して出力することにしました。
サンプルデータファイルhello_sjis.txt
こんにちは①ですで試したところ丸付き数字もちゃんと表示されました。
なお、Data.Text.ICU.Convert.converterNamesを実行してみるとcp932は含まれていないのですが、 ICU Demonstration - Converter Explorerを見ると、ibm-923_P100-1998とibm-942_P12A-1999のエイリアスになっています。Data.Text.ICU.Convert.aliasesで確認できます。
Prelude Data.Text.ICU.Convert> Data.Text.ICU.Convert.aliases "cp932" Loading package bytestring-0.9.1.10 ... linking ... done. Loading package array-0.3.0.2 ... linking ... done. Loading package containers-0.4.0.0 ... linking ... done. Loading package deepseq-1.1.0.2 ... linking ... done. Loading package text-0.11.0.6 ... linking ... done. Loading package text-icu-0.6.3.4 ... linking ... done. ["ibm-943_P15A-2003","ibm-943","Shift_JIS","MS_Kanji","csShiftJIS","windows-31j","csWindows31J","x-sjis","x-ms-cp932","cp932","windows-932","cp943c","IBM-943C","ms932","pck","sjis","ibm-943_VSUB_VPUA"] Prelude Data.Text.ICU.Convert> aliases "ibm-943_P15A-2003" ["ibm-943_P15A-2003","ibm-943","Shift_JIS","MS_Kanji","csShiftJIS","windows-31j","csWindows31J","x-sjis","x-ms-cp932","cp932","windows-932","cp943c","IBM-943C","ms932","pck","sjis","ibm-943_VSUB_VPUA"] Prelude Data.Text.ICU.Convert> aliases "ibm-942_P12A-1999" ["ibm-942_P12A-1999","ibm-942","ibm-932","cp932","shift_jis78","sjis78","ibm-942_VSUB_VPUA","ibm-932_VSUB_VPUA"]
それとtext-icuはCのライブラリを呼び出しているので、あまり大きな文字列を渡すのは避けた方が良いです。上のコードはサンプルなのでファイル全体にしていますが、サイズが大きいファイルのときは行単位にしたほうがよいと思います。
main = do cs <- getContents
putStr $ numbering cs
where
numbering :: String -> String
numbering cs = unlines $
map (format $ nwidth $ length $ lines cs) (zipLineNumber $ lines cs)
zipLineNumber :: [String] -> [(Int, String)]
zipLineNumber xs = zip [1..] xs
nwidth :: Int -> Int
nwidth len = ceiling $ logBase 10 $ fromIntegral $ len + 1
format :: Int -> (Int, String) -> String
format width (n, line) = rjust width (show n) ++ " " ++ line
rjust :: Int -> String -> String
rjust width s = replicate (width - length s) ' ' ++ s
main = getContents >>= putStr . unlines . numbering . lines
where
numbering :: [String] -> [String]
numbering ls = map (format $ nwidth $ length ls) (zipLineNumber ls)
zipLineNumber :: [String] -> [(Int, String)]
zipLineNumber = zip [1..]
nwidth :: Int -> Int
nwidth = ceiling . logBase 10 . fromIntegral . (1 +)
format :: Int -> (Int, String) -> String
format width (n, line) = rjust width (show n) ++ " " ++ line
rjust :: Int -> String -> String
rjust width s = replicate (width - length s) ' ' ++ s