将函数应用于文件(如果存在)

时间:2015-09-19 15:36:02

标签: haskell

我有一个函数将函数应用于文件(如果存在):

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 个答案:

答案 0 :(得分: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的类型中,我们看到两个Monads的组成

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

当我们看到两个monad的组合时,我们可以预期它可以被替换为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的类型更改为不具有两个monad的组合,而是对单个monad具有MonadPlus约束

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

这可以使用Control.Monad中的guard进行重写,并使用更通用的名称。

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)

如果你改造程序以使用monad样式类,它可能包含一个代码片段,如

import Control.Monad.IO.Class

(MonadPlus m, MonadIO m) =>
    ...
    guardBy (liftIO . doesFileExist) filename >>= liftIO . readFile