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

Eri*_*ric 4 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
Run Code Online (Sandbox Code Playgroud)

用法示例:

applyToFile readFile "/tmp/foo"
applyToFile (\p -> writeFile p "bar") "/tmp/foo"
Run Code Online (Sandbox Code Playgroud)

可以添加一个抽象级别:

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 
Run Code Online (Sandbox Code Playgroud)

这允许使用像:

applyToFileIf (\p -> doesFileExist p >>= (pure . not)) (\p -> writeFile p "baz") "/tmp/baz"
Run Code Online (Sandbox Code Playgroud)

我觉得我只是划伤了表面,并且有一个更通用的模式隐藏.
是否有更好的抽象或更惯用的方法来做到这一点?

Cir*_*dec 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
Run Code Online (Sandbox Code Playgroud)

applyToIf我们看到两种Monads的组成类型

                                           Maybe is a monad ---v 
applyToIf :: Monad m => (a -> m Bool) -> (a -> m b) -> a -> m (Maybe b)
                   ^------------- m is a monad -------------^
Run Code Online (Sandbox Code Playgroud)

当我们看到两个monad的组合时,我们可以预期它可以被替换为monad变换器堆栈和一些描述monad变换器添加的类.该MaybeT变压器替换m (Maybe a)

newtype MaybeT m a = MaybeT { runMaybeT :: m (Maybe a) }
Run Code Online (Sandbox Code Playgroud)

并增加MonadPlusm可以做的事情.

instance (Monad m) => MonadPlus (MaybeT m) where ...
Run Code Online (Sandbox Code Playgroud)

我们将改变类型applyToIf为不具有两个monad的组合,而是MonadPlus对单个monad 具有约束

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
Run Code Online (Sandbox Code Playgroud)

这可以根据guard来自Control.Monad并给出更通用的名称来重写.

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
Run Code Online (Sandbox Code Playgroud)

第二个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
Run Code Online (Sandbox Code Playgroud)

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)
Run Code Online (Sandbox Code Playgroud)

如果您重新编写程序以使用monad样式类,则可能包含类似的代码段

import Control.Monad.IO.Class

(MonadPlus m, MonadIO m) =>
    ...
    guardBy (liftIO . doesFileExist) filename >>= liftIO . readFile
Run Code Online (Sandbox Code Playgroud)