2015-09-19 2 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, мы можем ожидать, что он может быть заменен стеком трансформатора монады и некоторым классом, описывающим, что добавляет этот трансформатор монады. MaybeT трансформатор заменяет m (Maybe a)

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

И добавляет MonadPlus до какой m может сделать.

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

Мы изменим тип applyToIf, чтобы не иметь состав двух монад и вместо того, чтобы иметь 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 

Это может быть переписан с точки зрения 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 

Второй 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 
Смежные вопросы