Wikiエンジンの開発

13.3 CGIの処理


さて本日は"categorize"関数に挑戦します。
定義はこんな感じ。

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


その前に確認。
"categorize"関数が適用される値は以下のようなタプルのリストでした。

[(ラベル1, 値1), (ラベル2, 値2), (ラベル3, 値3), ...]


また"Config"型の定義はこんな感じ。

type Config = [(String, String)]


ぴったり一致しますね。
では中身です。
おっと、ラムダ式が登場しています。ここから見てきます。

\(a,_) (b,_) -> category a == category b


タプル二つを引数とするラムダ式ですね。
どちらのタプルも第一要素(ラベル)だけ使っています。


ここでもまずイメージします。
おそらく、"Config"型データをカテゴリでグループ分けするためのラムダ式じゃないかなぁ。


次に"category"関数です。

  • Config.hs
category = fst . break (== '.')


"fst"関数、"break"関数、いずれも既出の関数です。
なるほど、タプルのラベルは(おそらく)コロン'.'でこんな感じに書かれているんでしょう。

カテゴリ名.ラベル名


そして、"category"関数はこのカテゴリ名を取り出す関数。
さらに、上記ラムダ式はカテゴリ名が等しいかどうかを判定する関数なわけですね。


実際に添付されているconfigファイルを見てみると…

  • config
# LazyLines Config File
#
#   # Comment
#   category.key = value

database.directory = var
database.encoding  = euc-jp
template.directory = template
urlmapper.cgiurl   = ./
urlmapper.rewrite  = False


予想した通りですね。
ちょっとだけ試してみます。

\(a,_) (b,_) -> category a == category b
\("database.directory", "var"), ("database.encoding", "euc-jp") -> category "database.directory" == category "database.encoding"
\("database.directory", "var"), ("database.encoding", "euc-jp") -> "database" == "database"
\("database.directory", "var"), ("database.encoding", "euc-jp") -> True


では"categorize"関数に戻ります。
あと"categorize"関数で分からないのが、"groupBy"関数。
grepしても使っているところしか見つからなかったので、
いつものようにここで確認します。

  • Listモジュール
groupBy                 :: (a -> a -> Bool) -> [a] -> [[a]]
groupBy eq []           =  []
groupBy eq (x:xs)       =  (x:ys) : groupBy eq zs
                           where (ys,zs) = span (eq x) xs


まぁ、関数名から何となく想像は付くのですが、追ってみます。
おなじみの再帰ですね。
第二引数のリスト要素全てに対して処理をしていきます。
where節で現れる"span"関数も既出ですね。
まずは簡単な例で試してみます。

Prelude List> groupBy (\x y -> x == y) [1, 1, 2, 2, 2, 3, 3, 3]
[[1,1],[2,2,2],[3,3,3]]
Prelude List> groupBy (\x y -> x == y) [1, 1, 2, 2, 2, 3, 3, 3, 1, 2, 3]
[[1,1],[2,2,2],[3,3,3],[1],[2],[3]]


元々離れた場所にいると同じグループにはならないんですね。
ちょっといまいちですね。


先のconfigファイルの例を使って手動で展開すると煩雑になるので、
上の例で試してみます。

groupBy (\x y -> x == y) [1, 1, 2, 2, 2, 3, 3, 3]
(1:ys) : groupBy (\x y -> x == y) zs


ここで"ys"、"zs"はwhere節の中で定義されています。

where (ys,zs) = span (eq x) xs


つまり、

(ys, zs) = span (\y -> 1 == y) [1, 2, 2, 2, 3, 3, 3]
([1], [2, 2, 2, 3, 3, 3])


さらに進めます。

(1:[1]) : groupBy (\x y -> x == y) [2, 2, 2, 3, 3, 3]

(1:[1]) :
    (2:[2,2]) :
    groupBy (\x y -> x == y) [3, 3, 3]

(1:[1]) :
    (2:[2,2]) :
        (3:[3,3,3]) :
            groupBy (\x y -> x == y) []

(1:[1]) :
    (2:[2,2]) :
        (3:[3,3,3]) : []

[1,1] :
    [2,2,2] :
        [[3,3,3,3]]    -- ここが私の中ではポイント(悩んだところ)でした!
                       -- 3:[] → [3]
                       -- [3]:[] → [[3]]

[1,1] :
    [[2,2,2], [3,3,3,3]]

[[1,1], [2,2,2], [3,3,3]]


これを実際の"categorize"関数に当てはめると、ラベルのカテゴリ名でグループ分けされるわけです。


今日はここまで!
明日は"reduce"関数です。