練習問題

入門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単語と認識してるな。