我有一个函数将函数应用于文件(如果存在):
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"
我感觉我只是划伤了表面,并且隐藏了更通用的图案 是否有更好的抽象或更惯用的方法来做到这一点?
答案 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