我有一个函数将函数应用于文件(如果存在):
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)
我觉得我只是划伤了表面,并且有一个更通用的模式隐藏.
是否有更好的抽象或更惯用的方法来做到这一点?
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)
并增加MonadPlus了m可以做的事情.
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)