GHCライブラリ

Haskellの標準ライブラリを少し眺めていたので、メモ。GHC6.6がリリースされてるけど、6.4.2しか調べてない。SRFIと違って、名前から機能が類推しやすくてよい。
http://www.haskell.org/ghc/docs/6.4.2/html/libraries/index.html


  • ファイル入出力

  • http://www.sampou.org/haskell/report-revised-j/io.html
    ファイルやディレクトリを作ったり、消したり、存在するか確認するには、System.Directoryに色々

    import System.IO
    
    cat fname 
     = (openFile fname ReadMode)>>=(\h -> (cat_main h)>>(hClose h))
     where
      cat_main h
       = (hIsEOF h)
         >>=(\eof -> if eof then (return ()) else (hGetLine h)>>=putStrLn>>(cat_main h))

    openできなかったときは、エラー処理〜とか考えなくていいのは便利だね


  • ネットワーク

  • SocketとかCGIとかあるらしい。Socketは、普通のSocketにMonadを被せて使いにくくしたもの。ここSchemeコードをHaskellで書き直しただけの適当なSMTPクライアント

    import Network.Socket
    import Network.BSD
    --send_mail 25 "202.xxx.yyy.zzz" "from@xxx.co.jp" "to@yyy.ne.jp" "hello!"
    
    send_mail port host mail_from rcpt_to  message =
     do{s<-(socket AF_INET Stream 0);
        addr <- (inet_addr host)>>=(\h -> return (SockAddrInet port h));
         (connect s addr)
         >>(check_reply_code s 220)
         >>(getHostName>>=(\h -> (send_command s ("HELO "++h)  250)))
         >>(send_command s ("MAIL FROM: <"++ mail_from ++ ">") 250)
         >>(send_command s ("RCPT TO: <" ++ rcpt_to ++ ">") 250)
         >>(send_command s "DATA" 354)
         >>(main_loop s (lines message))
         >>(send_command s "." 250)
         >>(send_command s "QUIT" 221)
         >>(sClose s)}
     where
       send_command sock command  suc_code =
         (send sock (command++"\r\n"))>>(check_reply_code sock suc_code)
       check_reply_code sock code =
         (recv sock 512)
         >>=(\s -> if (take 3 s)==(show code)
                   then return ()
                   else (sClose sock)>>(fail ("smtp error :" ++ s)))
       main_loop sock messages = 
         case messages of
           [] -> return ()
           x:xs | (x==".") -> (send sock "..\r\n")>>(main_loop sock xs)
                | otherwise -> (send sock (x++"\r\n"))>>(main_loop sock xs)

    手続き型言語で書くのと殆ど変わらないな。ローカル関数が使えるので、その分見やすくはあるかもしれない。何故かgetHostByNameが上手く動かない。それから、ある程度の規模になると、failした時の後始末が、あちこちに分散して気持ち悪いかも。dynamic-windがほしい。Haskellで書けるんだと思うけど。そのうち、簡単なサーバーでも書いてみよう。


  • デバッグ・テスト

  • Haskellデバッグ大変らしい。てか、Buchbergerアルゴリズム書いたときに大変だった。monadのせいで"printfデバッグ"すら出来ないし(出来ないことはないかもしれないけど、面倒くさい)。一応、デバッグ用のTraceと、テストを自動で生成するQuickCheckなるものが。そのうち、調べる。QuickCheckは、このへんの例が分かりやすい


  • printf

  • 何に使うのか知らないけど、みんなprintf好きね

    import Text.Printf
    main = printf "Today:%d/%d\n" (10::Int) (15::Int)

    整数値の型は、デフォルトでは、Integerになるので、明示的にIntを指定しないといけない。不定数引数関数をどうやって実現してるのか、謎だ。気が向いたら、ソース読む。

    putStrLn ("Today:"++(show 10)++"/"++(show 15))

    より短くはある


  • FFI

  • CからHaskell呼ぶのも、HaskellからC呼ぶのもできるらしい。C以外に、JavaとかPerlとかできるらしい。あと、Windowsで、DLL呼べる。

    どんなにライブラリが貧弱でも、
    DLLさえ呼べれば
    なんとかなるよ!
                         
                     ハ_ハ  
                   ('(゚∀゚∩ なるよ!
                    ヽ  〈 
                     ヽヽ_)

    とりあえず、簡単なMessageBox出す例と、ゴミ箱空にする例。

    {-# OPTIONS -ffi #-}
    import Foreign
    import Foreign.C.String
    import Foreign.C.Types
    import Foreign.Ptr
    import System.Win32.DLL
    
    type HWND = Ptr Int32
    foreign import stdcall "dynamic" ptr_to_recycle_t
     :: FunPtr Int32 -> Ptr Int32 -> CString -> Int32 -> IO Int32
    foreign import stdcall "dynamic" ptr_to_msgbox_t
     :: FunPtr Int32 -> HWND -> CString -> CString -> CUInt -> IO CInt
    
    recycle =
      (loadLibrary "shell32")
        >>=(\hmod -> 
            (getProcAddress hmod "SHEmptyRecycleBinA")
             >>=(return . ptr_to_recycle_t . castPtrToFunPtr)
             >>=(\f -> (newCString "")>>=(\s -> (f nullPtr s 0)))
             >>(freeLibrary hmod))
    
    msgbox = 
     (loadLibrary "user32")
       >>=(\hmod ->
            (getProcAddress hmod "MessageBoxA")
             >>=(return . ptr_to_msgbox_t . castPtrToFunPtr)
             >>=(\f -> (newCString "the World!")
                        >>=(\s -> (newCString "MudaMudaMuda")
                                  >>=(\capt -> (f nullPtr s capt 0))))
             >>(freeLibrary hmod))

    一行目は、FFIを使うときに付けるお呪いらしい。"foreign import stdcall ..."はよく分からないけど、getProcAddressが返すのは、関数のアドレス(IO Addr)なので、Addr型を適切な型に変換するのに使う。stdcallは勿論呼び出し規約で、WINAPIなのでstdcall。Haskellのデータは軒並み、直接外部関数に渡すことはできないので、適切に変換する必要がある。整数とかはGHCが面倒見てくれるみたいだけど、文字列はダメっぽい

    しかし、これは、一旦(\hmod -> ...)に入ったら、終わるまで、このスコープ抜けれない。そして、この外では、基本的に、DLL関数も呼べない。大体、DLLをロードするのは、プログラムの最初の方で、開放するのは、終わりのほうだから、長いプログラムになってくると、(\hmod -> ...)内に全部書かないといけない。それは辛すぎる。それはもう、スタート地点に土管があって入ったら、凄い長い工程を経て、やっと外に出たらゴール前で、上のコインとり損ねたやんーって話ですよ(意味不明)

    まあ、そのためのunsafePerformIOなのかもしれないけど、そんなの使うくらいなら、Haskell使ってないよって話で、ここらへんいい解決策ないのか。そういや、昔なんかで、仮想関数テーブルから、アドレス経由でメンバー関数呼ぼうと思ったら無理ということがあったけど、Haskellならできるな


  • グラフィックス

  • Graphics.HGLは、Windowsでは動かないとか、6.6では、Windows versionからは、HGLが削除されるとか。ぬー。Graphics.Win32.Windowや、Graphics.Rendering.OpenGLWindowsでも使える模様。まあ、OpenGL使えればいいや。あと、GHC標準ではないけど、wxHaskellとか。そのうち、このへん使って、何か書いてみよう


  • 並行プログラミング

  • Control.Concurrent。普通にスレッド作れるらしい。STMとかは、そのうち調べる


  • 日本語処理・マルチバイト文字列
    全く考慮されてないように思います。

    main = putStrLn "こんにちわ"

    は、即エラーで弾かれます(EUC-JPで保存すると、一応通るようだけど)


  • その他

  • 三角関数とか基本的な数値計算関数は、Floatingクラスにある(GHC.Float)。勝手にimportされるっぽい
    乱数関数は、System.Random
    Data.xxxに、ハッシュテーブルとか、Setとか色々なデータ構造。
    Parsec。これがなかったら、Haskell使ってないかもしれないパーサジェネレータ
    ネタ以外に使い道があるのか不明なTemplate Haskell(Language.Haskell.TH)
    何に使えるのかすら分からないArrow(Control.Arrow)