2015-09-19 57 views
4

我有一個函數應用到一個文件,如果它存在的函數:應用功能到一個文件,如果它存在

import System.Directory 
import Data.Maybe 

applyToFile :: (FilePath -> IO a) -> FilePath -> IO (Maybe a) 
applyToFile f p = doesFileExist p >>= apply 
    where 
    apply True = f p >>= (pure . Just) 
    apply False = pure Nothing 

用例:

applyToFile readFile "/tmp/foo" 
applyToFile (\p -> writeFile p "bar") "/tmp/foo" 

抽象層可以添加:

import System.Directory 
import Data.Maybe 

applyToFileIf :: (FilePath -> IO Bool) -> (FilePath -> IO a) -> FilePath -> IO (Maybe a) 
applyToFileIf f g p = f p >>= apply 
    where 
    apply True = g p >>= (pure . Just) 
    apply False = pure Nothing 

applyToFile :: (FilePath -> IO a) -> FilePath -> IO (Maybe a) 
applyToFile f p = applyToFileIf doesFileExist f p 

,讓喜歡用法:

applyToFileIf (\p -> doesFileExist p >>= (pure . not)) (\p -> writeFile p "baz") "/tmp/baz" 

我有這種感覺,我只是抓了表面,並有一個更通用的模式隱藏。
是否有更好的抽象或更習慣的方法來做到這一點?

+1

片斷這個問題可能屬於上[codereview.stackexchange.com(HTTP://代碼審查。 stackexchange.com/)。 – Cirdec

+4

更大的問題是,「如果文件存在,對文件做些什麼」是對競爭條件和安全漏洞的邀請。正確的做法幾乎總是對文件執行一些操作,並捕獲如果它尚不存在的異常。無論編程語言如何,情況都是如此。 – dfeuer

回答

6

applyToFileIf可以給出一個更通用的類型和更多的通用名稱

applyToIf :: Monad m => (a -> m Bool) -> (a -> m b) -> a -> m (Maybe b) 
applyToIf f g p = f p >>= apply 
    where 
    apply True = g p >>= (return . Just) 
    apply False = return Nothing 

applyToIf我們看到兩個型單子

          Maybe is a monad ---v 
applyToIf :: Monad m => (a -> m Bool) -> (a -> m b) -> a -> m (Maybe b) 
        ^------------- m is a monad -------------^ 

當我們看到兩種成分的組成monads,我們可以預料它可以用一個monad變壓器堆棧和一些描述那個monad變壓器增加的類來代替。該MaybeT變壓器替換m (Maybe a)

newtype MaybeT m a = MaybeT { runMaybeT :: m (Maybe a) } 

,並增加了MonadPlus什麼的m可以做。

instance (Monad m) => MonadPlus (MaybeT m) where ... 

我們將改變applyToIf類型不能有兩個單子的成分,而是有一個單子

import Control.Monad 

applyToIf :: MonadPlus m => (a -> m Bool) -> (a -> m b) -> a -> m b 
applyToIf f g p = f p >>= apply 
    where 
    apply True = g p 
    apply False = mzero 

這可能在guard方面從Control.Monad改寫一個MonadPlus約束並給出更通用的名稱。

guardBy :: MonadPlus m => (a -> m Bool) -> (a -> m b) -> a -> m b 
guardBy f g p = f p >>= apply 
    where 
    apply b = guard b >> g p 

第二g參數添加任何什麼guardBy可以做。 guardBy f g p可以用guardBy f return p >>= g代替。我們將放棄第二個參數。

guardBy :: MonadPlus m => (a -> m Bool) -> a -> m a 
guardBy f p = f p >>= \b -> guard b >> return p 

MaybeT變壓器增加了任何計算失敗的可能性。我們可以使用它來重新創建applyToIf或更一般地使用它來處理完整程序中的故障。

import Control.Monad.Trans.Class 
import Control.Monad.Trans.Maybe 

applyToIf :: Monad m => (a -> m Bool) -> (a -> m b) -> a -> m (Maybe b) 
applyToIf f g = runMaybeT . (>>= lift . g) . guardBy (lift . f) 

如果改爲返工程序使用單子樣式類,它可能包括像

import Control.Monad.IO.Class 

(MonadPlus m, MonadIO m) => 
    ... 
    guardBy (liftIO . doesFileExist) filename >>= liftIO . readFile 
相關問題