hnakamur’s blog

ものすごい勢いで忘れる私のために未整理でもいいからとりあえずメモ

ラベル Haskell の投稿を表示しています。 すべての投稿を表示
ラベル Haskell の投稿を表示しています。 すべての投稿を表示

2011-07-22

Haskellのdataとtypeとnewtypeの違い

Chapter 6. Using Typeclassesでdataとtypeとnewtypeの違いを読んで整理してみる。

参考:Newtype - HaskellWiki
The Haskell 98 Report: Declarations

  data type newtype
一言で言うと 新規型定義 シノニム 独立型定義
既存型との区別 - 区別されない(混在可) 区別される(混在不可)
データコンストラクタ数 1以上 - 1
フィールド数 0以上 - 1
まだ理解が浅いのでこの表はたぶん中途半端。

2011-07-19

GHCでサロゲートペアが使えるか試してみた

2 Lexical Structure6 Predefined Types and ClassesではCharはUnicodeの文字を表すことはわかるがサロゲートペアについては言及無し。

Data.CharのGeneralCategoryにSurrogateという文字があることに気付いて対応しているのかなと思い、実験してみた。環境はScientific Linux 6.0+GHC7.0.3。

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)
#endif
wgencatは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;;;;;
…(略)…

HaskellのData.Timeを使ってみた

System.Timeは廃止予定でData.Timeを使えとのこと。

timeパッケージをインストール。
$ 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

2011-07-18

GHCでtext-icuパッケージを使ってみた

HackageDB: text-icu-0.6.3.4
試した環境は、Scientific Linux 6.0、GHC 7.0.3。

インストール
$ sudo yum install libicu-devel
$ cabal install text-icu

SJISのファイルを読み込んでUTF-8に変換して表示するサンプル。
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のライブラリを呼び出しているので、あまり大きな文字列を渡すのは避けた方が良いです。上のコードはサンプルなのでファイル全体にしていますが、サイズが大きいファイルのときは行単位にしたほうがよいと思います。

2011-07-02

Haskellで行番号付きcatのサンプル

Amazon.co.jp: ふつうのHaskellプログラミング ふつうのプログラマのための関数型言語入門: 青木 峰郎, 山下 伸夫: 本のコードを少しだけ改良。行番号の桁数を6桁固定ではなく計算で求めるようにしてみました。

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

ポイントフリースタイルにして、さらにdoを(>>=)で書き換えた版です。numberingのシグネチャも変えました。
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

ブログ アーカイブ