Wikiエンジンの開発
12.3 CGIの処理
loadContext続き
昨日の続きです。
昨日は"parseConfigFile"関数にたどり着いた時点で終了でした。
"parseConfigFile"の中で呼び出されている関数を見ていきます。
まずは"parseLine"から。
ちなみに"parseLine"関数の引数は
"./config"ファイルの中身の文字列を行毎に分離した("lines"関数を適用した)文字列リスト…
の中の各文字列です。"concatMap"関数を通して文字列リストが適用されていますからね。
ちなみに(忘れがちな)"concatMap"関数の使い方はこんな感じ。
Prelude> concatMap (\x -> [x, x+1, x+2]) [1,2,3] [1,2,3,2,3,4,3,4,5]
つまり、第二引数のリスト要素一つ一つに対して適用して戻されるリストを集めて、
リストの階層を一つ取っ払う感じですね。以下の式と等価です。
concatMap (\x -> [x, x+1, x+2]) [1,2,3] concat $ map (\x -> [x, x+1, x+2]) [1,2,3] concat [[1,2,3], [2,3,4], [3,4,5]] [1,2,3, 2,3,4, 3,4,5]
それでは"parseLine"関数の中身はこんな感じ。
parseLine = parseLinePlain . dropComment
あら、まだネストしてますね。
一つ一つ見ていきます。
まずは"dropComment"関数から。
dropComment = fst . break (== '#')
なるほど、細かいことはさておき、'#'文字以降はコメントとして捨ててるような感じですね。
まずは"break"。
"break"関数はPreludeモジュールで定義されている関数です。
定義はこんな感じ。
span, break :: (a -> Bool) -> [a] -> ([a],[a]) span p [] = ([],[]) span p xs@(x:xs') | p x = (x:ys,zs) | otherwise = ([],xs) where (ys,zs) = span p xs' break p = span (not . p)
なるほど、"break p = span (not . p)"と置き換えられるわけですね。
ようやく最下層まできました。
"span"関数の型は"(a -> Bool) -> [a] -> ([a],[a])"です。
つまり、第二引数のリスト要素を受け取り、True/Falseを判定する関数とリストを引数にとり、
引数のリストの要素で構成されたリストのタプルを返す関数です。
…う〜ん、言葉で説明するとわけ分からんですね。
詳しく見ていきます。
"span"関数は繰り返し処理(再帰処理)です。
第二引数のリスト(breakの第二引数)が空リストになるまで続く繰り返し処理ですね。
繰り返し終端で空リストのタプルを返します。
繰り返し終端ではないパターンマッチ部を見てみます。
span p xs@(x:xs') | p x = (x:ys,zs) | otherwise = ([],xs) where (ys,zs) = span p xs'
んんん?"'"って何だ?
って別に特別な意味はないか。
"xs"と"xs'"は違う変数だよぉ、という意味ですね。
第二引数の先頭要素に"not . (== '#')"関数を適用した時にTrueの場合、
"span"関数は"(x:ys, zs)"というタプルを返します。
タプルの左要素は"x:ys"。
xは第二引数の第一要素です。
pが"not . (== '#')"なので、'#'じゃない文字です。
ysは…、where節内に定義されています。
(ys,zs) = span p xs'
第二引数の先頭以降のリストに対して、"span p"を適用した結果のタプル左要素です。
う〜ん、なんじゃらほい?
つまり、'#'が現れるまでは第二引数のリストの要素が続くわけですが、
'#'が現れると
otherwise = ([],xs)
にあるように、空リストが返ります。
つまり
"abcdefg hijklmn # yohpapa writtend."
という文字列が第二引数に指定された場合、
"abcdefg hijklmn "
が入ってくるわけですね。
次に"x:ys"の右要素である"ys"。
これもwhere節内に定義されています。
(ys,zs) = span p xs'
第二引数の先頭以降のリストに対して、"span p"を適用した結果のタプル右要素です。
これまた、う〜ん、なんじゃらほい?
"zs"が確定するのはずばりここ。
otherwise = ([],xs)
(not . (== '#'))関数がFalseを返す時、'#'を含む以降の文字列を指します。
これでようやく"dropComment"関数の役割が分かりました。
dropComment = fst . break (== '#')
つまり、"./config"ファイルの中身の各行に関して、
'#'以降の文章を捨てているわけですね。
"break (== '#')"関数の結果のタプルは('#'以前の文字列, '#'以降の文字列)となり、
これの左側要素だけを取り上げる("fst"関数を適用)わけですから。
次は"parseLinePlain"関数です。