最近使ったモジュールとか

Data.ConfigFile

設定ファイル読み込み用のパッケージ。
こんな感じでよく使ってる。

printUsage = print "..."

main = do
  args <- getArgs
  case args of
    []             -> pringUsage
    (configFile:_) -> do
      val <- readfile emptyCP{optionxform = id} configFile
      let cp = forceEither val -- 設定ファイル読み込みに失敗したら死ねばいい
      start cp

emptyCP の optionxform はデフォルトで toLower になってて、キー値が小文字にされちゃって嫌なので何もしないようにしてたり。


あと自分は "DEFAULT" しか使わなかったり、キー値が無ければさっさと終わってくれていいようなケースが多いので、

getDefault :: Get_C a => ConfigParser -> OptionSpec -> Either CPError a
getDefault cp opt = get cp "DEFAULT" opt

getForce :: Get_C a => ConfigParser -> OptionSpec -> a
getForce cp opt = forceEither $ getDefault cp opt

こんな補助関数使ってる。

System.Posix.Daemonize

その名の通りデーモン化してくれるパッケージ。
デーモン化するかどうかを Data.ConfigFile で読む設定ファイルに書いておけば便利かも。

-- "daemonize" の設定が true なら、デーモン化してから program cp を実行する
-- "daemonize" の設定が false なら、デーモン化せずに program cp を実行する
-- "daemonize" 設定が見つからなかったら例外投げて終了する
startWithDaemonize :: ConfigParser -> (ConfigParser -> IO ()) -> IO ()
startWithDaemonize cp program = daemonizeIf (getForce cp "daemonize" :: Bool)
  where
    daemonizeIf True  = daemonize $ program cp
    daemonizeIf False = program cp

デーモン化は、GHC7.1 までは -threaded を付けてコンパイルするとおかしなことになってたらしいのだけど、GHC7.2 以降では直ってるらしい。

System.Log

ログ表示用のパッケージ。
Data.ConfigFile でどういう風にログ出力するかを書いておくと便利かも。

openDebugLog :: ConfigParser -> String -> IO ()
openDebugLog cp name = do
  -- ここでのエラーは全部無視する
  handle (\(SomeException s) -> print s) $ do
    -- log.path でどのファイルに出力するか決められる
    let path = getForce cp "log.path"
    -- log.priority でどのレベル以上を出力するか決められる
    let priority = either (const WARNING) (read :: String -> Priority) $ getDefault cp "log.priority"
    h <- fileHandler path priority
    -- log.format でどんなフォーマットで出力するか決められる
    let format = either (const "[$time : $loggername : $prio] $msg") (id :: String -> String) $ getDefault cp "log.format"
    let fh = setFormatter h $ simpleLogFormatter format
    updateGlobalLogger name (setLevel priority . addHandler fh)

で、これを呼んでログファイルを開いたら、後はそこへ出力するだけ。

program cp = do
  openDebugLog cp "hogelog"
  infoM "hogelog" "info"
  errorM "hogelog" "error"

Database.MongoDB

MongoDB に繋いで各種処理を行うパッケージ。
詳細はあまり理解してないのだけど、繋いで適当にクエリ投げて結果取得するだけなら、サンプル見ればすぐ分かって便利。
あとはまあ、例外処理とか RAII とかやれば安心な気がする。

import Control.Exception (SomeException(SomeException),bracket,handle)
import Data.UString (UString)
import qualified Database.MongoDB as M
import Database.MongoDB (u,(=:))

-- MongoDB へ接続。失敗したら例外が投げられる
connectMongoDB :: M.HostName -> IO M.Pipe
connectMongoDB h = M.runIOE $ M.connect $ M.host h

-- クエリ関数。
-- find するとカーソルが得られるので、そこから rest で全部一気に持ってきてリストにして返す
find :: (MonadMVar m, Functor m) => UString -> M.Action m [M.Document]
find name = M.find (M.select [u"name"=:name] (u"things")) >>= M.rest

dbMain = do
  -- 何か問題が起きたらここでハンドルする
  handle (\(SomeException s) -> print s) $ do
    -- RAII 的なことをする
    bracket (connectMongoDB "xxx.xxx.xxx.xxx") M.close $ \pipe -> do
      -- エラーかどうかが Either で返されるので、
      -- Left だった場合に例外発生させるなら forceEither を使う
      resultOrError <- M.access pipe M.master (u"test") (find $ u"hoge")
      let result = forceEither resultOrError
      print result

Happstack

HTTP サーバーが簡単に作れるパッケージ。
とりあえずこれだけでサーバーが動く状態に。

import Happstack.Server
main = simpleHTTP nullConf $ ok "Hello, World!"

アクセスされたメソッドや URL に応じて処理を変えるには Routing の関数を msum する。

import Control.Monad (msum,mzero)
import Happstack.Server

-- URL で得られた解析を使って何かする。
-- ただし引数は 3 つまで。
doQuery :: [String] -> ...
doQuery xs | length xs >= 4 = mzero
           | otherwise      = ...

-- URL を nullDir になるまで再帰的に解析
paths f = paths' f []
  where
    paths' f xs = msum [nullDir >> f (reverse xs),
                        path (paths' f.(:xs))]

main = simpleHTTP nullConf $ msum [
  -- GET メソッドかつ hello/xxxx という URL を受けたら "Hello, xxxx" と表示する
  dir "hello" $ (\s -> methodM GET >> ok $ "Hello, "++s),
  -- query/xxx/yyy/zzz/... という URL を [xxx,yyy,zzz,...] というリストにして doQuery 関数を実行する
  dir "query" $ paths doQuery]

{-
http://sample.com/hoge
 → エラー
http://sample.com/hello/hoge
 → Hello, hoge
http://sample.com/hello/hoge/fuga
 → エラー
http://sample.com/query/hoge
 → doQuery [hoge] の実行結果
http://sample.com/query/hoge/fuga/moke
 → doQuery [hoge,fuga,moke] の実行結果
http://sample.com/query/hoge/fuga/moke/hage
 → エラー
-}

これと、あとは URL のクエリ部分を探すパッケージだけあればとりあえずやりたいことは出来たのでいいのだけど、他にもパッケージを見た感じだと Happstack.Server にはクッキー、ファイルサービング、ベーシック認証、プロキシサーブ、Validation、XSLT といろいろありすぎるし、HTML を動的に作るためのパッケージもチュートリアルの中で紹介されてたりとかしてやばい。HTTP サーバーってこんなに簡単に作れるのかとちょっと感動。