2009年10月27日火曜日

復習 「ふつうのHaskell」 2章

4797336021

ファイルの行数を数える

countline.hs (p.39)

短かいので do 式は使わずに、

main = getContents >>= \x -> print $ length $ lines x

関数合成を利用すると、(cf. Haskell の関数合成)

main = getContents >>= print . length . lines

 

ファイルの先頭から指定した行数だけ表示

head.hs (p.45)

こちらも do 式を使わず、

main = getContents >>= putStrLn . getlines 10
getlines n = unlines . take n . lines

 

ところで、これは次のようにも書ける。

main = getContents >>= printLines 10
printLines n = putStrLn . unlines . take n . lines

一体どちらのコードがいいのかな?

Introduction to QuickCheck – HaskellWiki によると、

… the side effecting monadic code is mixed in with the pure computation, making it difficult to test without moving entirely into a "black box" IO-based testing model.

先ほどの関数の型を比較すると、

*Main> :t getlines
getlines :: Int -> String -> String
*Main> :t printLines
printLines :: Int -> String -> IO ()

前者のの方が IO モナドを含まず、純粋な関数なのでテストしやすくて良いようだ。

 

ファイルの末尾から指定した行数を表示

tail.hs (p.51)

head.hs と同じようにして、

main = getContents >>= putStrLn . tailLines 3
tailLines n =  unlines . reverse . take n . reverse . lines

 

しかし、tailLines 関数を見ると、関数 lines – unlines, reverse – reverse が対になっており、真ん中に take 関数が置かれているように見える。関数を関数で挟みこむデコレータのような関数を定義してシンプルにできないものか…。(cf. Python のデコレータ式 (1))

次のように関数 f を 関数 x, y で挟む関数を次のように定義してみる。

(***) x y f z = y $ f $ x z

これは関数合成を使った方が見やすいかも。

(***) x y f = y . f . x 

これにより対になる関数を定義。

l = lines *** unlines
r = reverse *** reverse

上記の関数を試してみる。

*Main> l (r $ take 2) "hoge\npiyo\nfuga\n"
"piyo\nfuga\n"

動作したので takeLines を書きなおすと、

tailLines n = l $ r $ take n

関数合成を使うなら、

tailLines = l . r . take

 

全体は、

main = getContents >>= putStrLn . tailLines 3

(***) x y f = y . f . x 

l = lines *** unlines
r = reverse *** reverse

tailLines = l . r . take

うーん、返ってわかりずらくなったような気が… (@_@;)  QQQ