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節の中で定義(束縛)されています。
今日はこの辺で。