Subscribed unsubscribe Subscribe Subscribe

遅延I/Oとメモリリークのつづき

Haskell

id:maoe:20091108:1257701870の件をhaskell-ja(chaton)で相談してみたところ、nwnさんとnobsunさんに教えていただきました。ありがとうございました。

せっかくなので、こちらにもまとめを書いておきます。

先のエントリで意図していた例

元々意図していたことは、putStrを2回するなんて恣意的なものではなく、

  • ログファイルの解析をするとき、1ファイル(あるいは生のログストリーム)から複数種類の解析を行いたい場合
    • 頻出IPアドレスの提示とページビューの計算とユニークユーザ数の計算などを同時にしたい
遅延I/Oとメモリリーク - maoeのブログ

というシチュエーションを想定していた。これに対して、スレッドを使うとか姑息な手段ではなく、かつ関数プログラミング的に美しい方法を教えてもらった。

解決策: 関数を融合してfoldl

上記の例では

  • 頻出IPアドレスの提示
  • ページビューの計算
  • ユニークユーザ数の計算

という3つの計算を扱うが、いずれも行指向の計算になっている。従って、一行ごとに処理する関数を考える。それぞれ

reqentSourceAddrs :: Map IPAddr Frequency -> Log -> Map IPAddr Frequency
pageViews :: Integer -> Log -> Integer
uniqueUsers :: Integer -> Log -> Integer

という関数とすると、これらを組み合わせて

analyze :: [Log] -> (Integer, Integer, Map IPAddr Frequency)

な関数analyzeを作ればよい。リストを前から順に値に集約すると言えばfoldl。言われてみれば単純な話だった。

ソースコード全体は次のようになる。

発展的内容

さらに、nwnさんに発展的内容へのポインタをいただきました。

この辺りも参考になるんではないかと思います。left-fold を組み合わせて複雑な関数を作る: http://squing.blogspot.com/2008/11/beautiful-folding.html
コメント欄に Fold b は Applicative になるよーという指摘があります
それで pageViews :: Fold Log Integer; uniqUsers :: Fold Log Integer ... みたいにして
cfoldl' (Result <$> pageViews <*> uniqUsers <*> freqAddrs) . lines =<< readFile logfile

中略

とか書いてみたらそれっぽくなるかなーと
こなちゃんの反応: http://conal.net/blog/tag/zip/
Applicative はもっと評価されるべき

これはすごい。まだまだこういう発想がすっと出てくるにはほど遠いなあ。