2011-12-14 2 views
24

Каков правильный способ сделать это в Haskell?Удалить файл, если он существует

if exists "foo.txt" then delete "foo.txt" 
doSomethingElse 

До сих пор у меня есть:

import System.Directory 
main = do 
     filename <- getFileNameSomehow 
     fileExists <- doesFileExist filename 
     if fileExists 
      then removeFile filename 
      ??? 
     doSomethingElse 
+4

Функция 'doesFileExist' действительно является приглашением к условиям гонки. Он не должен существовать. – augustss

+7

@augustss: Как насчет того, чтобы мы просто переименовали его в 'didFileExistLastTimeIChecked'? –

+4

Я предлагаю 'didFileNotNeverExist'. – ehird

ответ

44

Вы бы лучше удаление файла и просто восстанавливается, если он не существует:

import Prelude hiding (catch) 
import System.Directory 
import Control.Exception 
import System.IO.Error hiding (catch) 

removeIfExists :: FilePath -> IO() 
removeIfExists fileName = removeFile fileName `catch` handleExists 
    where handleExists e 
      | isDoesNotExistError e = return() 
      | otherwise = throwIO e 

Это позволяет избежать состояния гонки кого-то удалить файл между вашим проверяет, существует ли он и удаляет его. В вашем случае это может быть неважно, но это хорошая практика.

Обратите внимание на строку import Prelude hiding (catch) - это потому, что прелюдия содержит более старые функции из обработки исключений, которые теперь устарели в пользу Control.Exception, которая также имеет функцию с именем catch; строка импорта просто скрывает прелюдию catch в пользу Control.Exception.

Тем не менее, это все еще оставляет более фундаментальный основной вопрос: как вы записываете условные обозначения в IO?

Ну, в данном случае, это будет достаточно просто сделать

when fileExists $ removeFile filename 

(с использованием Control.Monad.when). Но это полезно здесь, как это обычно бывает в Haskell, чтобы посмотреть на типы.

Обе ветви условного типа должны иметь одинаковый тип. Поэтому заполнить

if fileExists 
    then removeFile filename 
    else ??? 

мы должны посмотреть на тип removeFile filename; независимо от того, ???, он должен иметь тот же тип.

System.Directory.removeFile имеет тип FilePath -> IO(), поэтому removeFile filename имеет тип IO(). Таким образом, мы хотим, чтобы действие IO было результатом типа (), который ничего не делает.

Ну, цель return является построение действия, которое не имеет никакого эффекта, а просто возвращает значение константы, и return() имеет правильный тип для этого: IO() (или в более общем плане, (Monad m) => m()). Таким образом, ??? - это return() (который вы можете видеть, что я использовал в моем улучшенном фрагменте выше, ничего не делать, когда терпит неудачу, потому что файл не существует).

(Кстати, теперь вы должны быть в состоянии осуществить when с помощью return(), это очень просто :))

Не волнуйтесь, если вам трудно попасть в путь Haskell вещей вначале - это будет естественно во времени, и когда это произойдет, это очень полезно. :)

+4

Вот ссылка на документацию Control.Exception: http://hackage.haskell.org/packages/archive/base/latest/doc/html/Control-Exception.html - я не мог поместить ее в сообщение, потому что я У меня достаточно репутации:/ – ehird

+2

Спасибо, это было действительно полезно! – yogsototh

+1

Нет проблем - насколько доступно материал для чтения, есть отличный [Learn You a Haskell] (http://learnyouahaskell.com/) и [Real World Haskell] (http://book.realworldhaskell.org/), но Думаю, вы уже знаете об этом. Однако я могу рекомендовать [Hoogle] (http://www.haskell.org/hoogle/) возможность поиска по типу как неоценимого для программирования Haskell. – ehird

10

(Примечание: ответ ehird делает очень хороший момент относительно состояния гонки Следует иметь в виду, читая мой ответ, который игнорирует. Следует также отметить, что императивный псевдокод, представленный в вопросе, также страдает от той же проблемы.)

Что определяет имя файла? Дана ли она в программе или предоставляется пользователем? В вашем императивном псевдокоде это постоянная строка в программе. Я предполагаю, что вы хотите, чтобы пользователь предоставил его, передав его в качестве первого аргумента командной строки для программы.

Тогда я предлагаю что-то вроде этого:

import Control.Monad  
import System.Directory 
import System.Environment 

doSomethingElse :: IO() 

main = do 
    args <- getArgs 
    fileExists <- doesFileExist (head args) 
    when fileExists (removeFile (head args)) 
    doSomethingElse 

(Как вы можете видеть, я добавил тип подписи doSomethingElse, чтобы избежать путаницы).

Я импортирую System.Environment для getArgs функция. В случае, если файл, о котором идет речь, просто задается константной строкой (например, в вашем императивном псевдокоде), просто удалите все элементы args и заполните константную строку везде, где у меня есть head args.

Control.Monad импортирован для получения функции when. Обратите внимание, что эта полезная функция не ключевое слово (например, if), а обычная функция. Давайте посмотрим на его тип:

when :: Monad m => Bool -> m() -> m() 

В вашем случае m является IO, так что вы можете думать о when как функцию, которая принимает Bool и действие ввода-вывода и выполняет действие, только если Bool является True. Конечно, вы могли бы решить вашу проблему с помощью if s, но в вашем случае when читает намного яснее. По крайней мере, я так думаю.

Добавление: Если вы, как я сделал в первый, возникает ощущение, что when некоторая магическая и сложная техника, это очень полезно попытаться определить функцию самостоятельно. Я обещаю вам, что он мертв просто ...

+4

Это также поучительно думать о * почему * просто определить «когда» и какие другие последствия имеют. Императивные кодовые блоки - это первоклассные объекты в Haskell, которые могут соответствовать нескольким языкам. –

Смежные вопросы