Wikiエンジンの開発

12.3 CGIの処理


まず始めはmainアクションからです。

main = do ctx <- loadContext "./config"
          runCGI (appMain ctx)


シンプルですね。
日本語的に書くと、設定ファイル"./config"を読み込んで、runCGIを引数appMain ctxに適用する。
では一つ一つ見ていこう。

loadContextを見てみる

loadContext関数はLazyLine.hsで定義されています。
定義はこんな感じ。

  • LazyLine.hs
loadContext :: String -> IO Context
loadContext path =
    do conf <- loadConfig path
       return $ Context (Database.fromConfig (confLookup "database" conf))
                        (Template.fromConfig (confLookup "template" conf))
                        (URLMapper.fromConfig (confLookup "urlmapper" conf))


引数にStringを受け、
IOモナドであるIO String、つまりStringを返すアクションを返します。
ではさらに、loadConfig関数を見てみましょう。

  • Config.hs
loadConfig :: FilePath -> IO [(String, Config)]
loadConfig path = return . parseConfigFile =<< readFile path


こちらはFilePath型の引数を受けて、[(String, Config)]を返すアクションを返します。
…まだまだ付いていけます。
中身を見ると、関数が合成されています。
"return"(左側)と"parseConfigFile =<< readFile path"(右側)です。
"return"(左側)は、IOモナド化する演算子ですね。
問題は右側です。
演算子らしき"=<<"。形から察するに右辺の結果を左辺に渡しているような。
これはMonadクラスのメソッドですね。演算子(>>=)をひっくり返したやつです。
定義はこんな感じ。

(=<<)            :: Monad m => (a -> m b) -> m a -> m b
f =<< x          =  x >>= f


ということで、右辺を見てみます。
右辺は

readFile path


です。pathはloadConfig関数の引数です。つまり"./config"、文字列ですね。
じゃぁ、readFileはというと、

readFile 関数はファイルを読みそのファイルの内容を文字列として返す。

readFile :: FilePath -> IO String

http://www.sampou.org/haskell/report-revised-j/io-13.html#standard-io-functions


とのこと。ライブラリの関数なわけですね。
ちなみに"FilePath"は、Preludeモジュールで以下のように定義された型です。

type  FilePath = String


では(=<<)演算子の左辺"parseConfigFile"ですが、この定義はこんな感じ。

  • Config.hs
parseConfigFile :: String -> [(String, Config)]
parseConfigFile = map reduce . categorize . concatMap parseLine . lines
  where
    reduce :: Config -> (String, Config)
    reduce kvs = (category . fst . head $ kvs,
                  map (\(k,v) -> (itemname k, v)) kvs)

    categorize :: Config -> [Config]
    categorize = groupBy (\(a,_) (b,_) -> category a == category b)

    -- a key of the config item = "category.itemname"
    category = fst . break (== '.')
    itemname = tail . snd . break (== '.')

    parseLine = parseLinePlain . dropComment

    parseLinePlain line
        | isBlank line = []
        | otherwise    = let (k, ('=':v)) = break (== '=') . strip $ line
                         in [(strip k, strip v)]

    dropComment = fst . break (== '#')


げげっ!長い!!
一つ一つ噛み砕いて見ていこう。
まず"parseConfigFile"関数の型は、引数に"String"を受け、"[(String, Config)]"を返します。
"Config"型の定義はこんな感じ。

  • Config.hs
type Config = [(String, String)]


なるほど、"String"のタプルのリストですね。
では関数の中身。

parseConfigFile = map reduce . categorize . concatMap parseLine . lines


この中の"reduce"、"categorize"、"parseLine"が続くwhere節の中で定義(束縛)されています。


今日はこの辺で。