「入門Haskell―はじめて学ぶ関数型言語」 p.33 より。
wordScan 利用版の wordsCount で,同じように `~’ を1単語と見なす拡張を追加しなさい。またその際,wordScan を積極的に利用すること。
これはめんどくさかった。
まずは`~’に対応していない wordScan 利用版。
wordsCount2 str = outWords str
where wordScan f [] = 0
wordScan f (c:cs)
| isAlphaNum c = f (inWords cs)
| otherwise = outWords cs
outWords str = wordScan (\n -> 1 + n) str
inWords str = wordScan id str
それから`~’に対応してるけど wordScan を利用しない版。
wordsCount str = outWords str
where outWords [] = 0
outWords (c:cs)
| c == '`' = 1 + inQuote cs
| isAlphaNum c = 1 + inWords cs
| otherwise = outWords cs
inWords [] = 0
inWords (c:cs)
| c == '`' = 1 + inQuote cs
| isAlphaNum c = inWords cs
| otherwise = outWords cs
inQuote [] = 0
inQuote (c:cs)
| c == '\'' = outWords cs
| otherwise = inQuote cs
このうちの条件分岐を wordScan の中に押し込めるんだけど,次に呼び出す関数との組み合わせをどうやったらうまく整理できるか,でずいぶんと試行錯誤した。
表にするとこんなふうになる。縦の軸が wordScan を呼び出す関数,横の軸が分岐条件で,各欄に書いてあるのはカウントアップのための関数と次に呼び出す関数。
| c == ‘`’ | c == ‘\” | isAlphaNum c | otherwise | |
|---|---|---|---|---|
| outWords | (1+) inQuote | outWords | (1+) inWords | outWords |
| inWords | (1+) inQuote | outWords | id inWords | outWords |
| inQuote | id inQuote | outWords | id inQuote | inQuote |
右上の2×2マスが拡張前の版に相当する。この場合はどの関数(outWOrdsとinWords)から呼び出されても次に呼び出す関数は,条件ごとにおなじだった。
それが,拡張版ではおなじ条件でもどの関数から呼び出されたかによって,次に呼び出す関数が変化する。しかも isAlphaNum c のときと otherwise のときの2つもだ。
だからこの2つのときに呼び出すべき関数を wordScan に引数として与えてやることにした。
wordsCount2 str = outWords str
where wordScan f g1 g2 [] = 0
wordScan f g1 g2 (c:cs)
| c == '`' = (1+) (inQuote cs)
| c == '\'' = outWords cs
| isAlphaNum c = f (g1 cs)
| otherwise = g2 cs
outWords str = wordScan (1+) inWords outWords str
inWords str = wordScan id inWords outWords str
inQuote str = wordScan id inQuote inQuote str
wordScan は関数を3つ(f,g1,g2)と文字列を1つ引数にとるようになった。f はカウントアップのための関数,g1とg2はそれぞれisAlphaNum c ,otherwise のときに呼び出す関数だ。
で,実行結果。
*Main> wordsCount2 "`Theory of Everything' by Greg Egan" 4
うん,ちゃんと `~’ を1単語と認識してるな。