2014年2月15日土曜日

開発環境

Real World Haskell―実戦で学ぶ関数型言語プログラミング(Bryan O'Sullivan (著)、 John Goerzen (著)、 Don Stewart (著)、山下 伸夫 (翻訳)、伊東 勝利 (翻訳)、株式会社タイムインターメディア (翻訳)、オライリージャパン)の9章(入出力事例研究: ファイルシステム検索ライブラリ)、9.7(走査方法の制御)の練習問題 3.を解いてみる。

その他参考書籍

練習問題 3.

コード(BBEdit, Emacs)

BetterPredicate.hs

{-# OPTIONS -Wall -Werror #-}
module BetterPredicate where

import Control.Exception (handle, bracket, SomeException)
import System.IO (IOMode(..), openFile, hClose, hFileSize)
import System.FilePath (takeExtension)
import ControlledVisit (Info(..), getInfo)

main :: IO ()
main = do
    mapM_ (\path -> do
              info <- getInfo path
              putStrLn $ "ファイルパス: " ++ path
              putStrLn $ "サイズ: " ++ (show $ sizeP info)
              putStr "拡張子が「.hs」かつ、サイズが2KBより大きいかどうか: "
              print $ myTest1 info
              putStr "拡張子が「.cpp」または、サイズが2KBより小さいかどうか: "
              print $ if sizeP info < 0
                      then False
                      else myTest2 info)
          ["./BetterPredicate.hs", "./ControlledVisit.hs", "./Sample.cpp",
           "./temp1_folder"]
    
getFileSize :: FilePath -> IO (Maybe Integer)
getFileSize path = handle nothing $
    bracket (openFile path ReadMode) hClose $ \h -> do
        size <- hFileSize h
        hClose h
        return (Just size)

nothing :: SomeException -> IO (Maybe Integer)
nothing _ = return Nothing

equalP :: (Eq a) => (Info -> a) -> a -> (Info -> Bool)
equalP f k = \info -> f info == k

liftP :: (a -> b -> c) -> (Info -> a) -> b -> (Info -> c)
liftP q f k info = f info `q` k

greaterP :: (Ord a) => (Info -> a) -> a -> (Info -> Bool)
greaterP = liftP (>)

lesserP :: (Ord a) => (Info -> a) -> a -> (Info -> Bool)
lesserP = liftP (<)

liftP2 :: (a -> b -> c) -> (Info -> a) -> (Info -> b) -> (Info -> c)
liftP2 q f g info = f info `q` g info

sizeP :: Info -> Integer
sizeP info = maybe (-1 :: Integer) id (infoSize info)

andP :: (Info -> Bool) -> (Info -> Bool) -> (Info -> Bool)
andP = liftP2 (&&)

orP :: (Info -> Bool) -> (Info -> Bool) -> (Info -> Bool)
orP = liftP2 (||)

liftPath :: (FilePath -> a) -> (Info -> a)
liftPath f info = f (infoPath info)

myTest1 :: Info -> Bool
myTest1 = (liftPath takeExtension `equalP` ".hs") `andP`
          (sizeP `greaterP` 2000)

myTest2 :: Info -> Bool
myTest2 = (liftPath takeExtension `equalP` ".cpp") `orP`
          (sizeP `lesserP` 2000)

ControlledVisit.hs

{-# OPTIONS -Wall -Werror #-}
module ControlledVisit where

import System.Directory (Permissions, getDirectoryContents, getPermissions,
                         getModificationTime, searchable)
import System.IO (IOMode(..), openFile, hClose, hFileSize)
-- System.Timeは非推奨
import Data.Time.Clock (UTCTime)
import System.FilePath ((</>))
import Control.Exception (handle, bracket, SomeException)
import Control.Monad (liftM, forM)

data Info = Info { infoPath :: FilePath,
                   infoPerms :: Maybe Permissions,
                   infoSize :: Maybe Integer,
                   infoModTime :: Maybe UTCTime} deriving (Eq, Ord, Show)

getInfo :: FilePath -> IO Info
getInfo path = do
    perms <- maybeIO (getPermissions path)
    size <- maybeIO (bracket (openFile path ReadMode) hClose hFileSize)
    modified <- maybeIO (getModificationTime path)
    return (Info path perms size modified)

maybeIO :: IO a -> IO (Maybe a)
maybeIO act = handle nothing (Just `liftM` act)

nothing :: SomeException -> IO (Maybe a)
nothing _ = return Nothing

traverse :: ([Info] -> [Info]) -> FilePath -> IO [Info]
traverse order path = do
    names <- getUsefulContents path
    contents <- mapM getInfo (path:map (path </>) names)
    liftM concat $ forM (order contents) $ \info -> do
        if isDirectory info && infoPath info /= path
        then traverse order (infoPath info)
        else return [info]


getUsefulContents :: FilePath -> IO [String]
getUsefulContents path = do
    names <- getDirectoryContents path
    return (filter (`notElem` [".", ".."]) names)

isDirectory :: Info -> Bool
isDirectory = maybe False searchable . infoPerms

-- 帰りがけ順(子ディレクトリが親ディレクトリより先に返る)に操作する制御関数
order1 :: [Info] -> [Info]
order1 = foldr (\i acc -> if isDirectory i then acc ++ [i] else i:acc) []

入出力結果(Terminal, runghc)

$ runghc BetterPredicate.hs 
ファイルパス: ./BetterPredicate.hs
サイズ: 2195
拡張子が「.hs」かつ、サイズが2KBより大きいかどうか: True
拡張子が「.cpp」または、サイズが2KBより小さいかどうか: False
ファイルパス: ./ControlledVisit.hs
サイズ: 1867
拡張子が「.hs」かつ、サイズが2KBより大きいかどうか: False
拡張子が「.cpp」または、サイズが2KBより小さいかどうか: True
ファイルパス: ./Sample.cpp
サイズ: -1
拡張子が「.hs」かつ、サイズが2KBより大きいかどうか: False
拡張子が「.cpp」または、サイズが2KBより小さいかどうか: False
ファイルパス: ./temp1_folder
サイズ: -1
拡張子が「.hs」かつ、サイズが2KBより大きいかどうか: False
拡張子が「.cpp」または、サイズが2KBより小さいかどうか: False
$

0 コメント:

コメントを投稿