2011-12-14 19 views
24

Qual è il modo corretto di farlo in Haskell?Rimuovere il file se esiste

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

Finora ho:

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

La funzione 'doesFileExist' è davvero un invito alle condizioni di gara. Non dovrebbe esistere. – augustss

+7

@augustss: che ne dici di rinominarlo in 'didFileExistLastTimeIChecked'? –

+4

Suggerisco 'didFileNotNeverExist'. – ehird

risposta

44

Si sarebbe meglio rimuovere il file e semplicemente recuperare se non esiste:

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 

questo modo si evita la condizione di competizione di qualcuno l'eliminazione del file tra il codice verifica se esiste e lo elimina. Potrebbe non avere importanza nel tuo caso, ma è comunque una buona pratica.

Nota la riga import Prelude hiding (catch): questo perché il Preludio contiene funzioni meno recenti dalla gestione delle eccezioni che ora sono deprecate a favore di Control.Exception, che ha anche una funzione denominata catch; la riga di importazione nasconde semplicemente il Prelude catch in favore di Control.Exception.

Tuttavia, ciò lascia ancora la domanda di fondo più fondamentale: come si scrivono condizionali in IO?

Beh, in questo caso, sarebbe sufficiente a fare semplicemente

when fileExists $ removeFile filename 

(usando Control.Monad.when). Ma è utile qui, come di solito è in Haskell, guardare i tipi.

Entrambi i rami di un condizionale devono avere lo stesso tipo. Quindi per riempire

if fileExists 
    then removeFile filename 
    else ??? 

dovremmo guardare il tipo di removeFile filename; qualunque sia lo ???, deve avere lo stesso tipo.

System.Directory.removeFile ha il tipo FilePath -> IO(), quindi removeFile filename ha il tipo IO(). Quindi quello che vogliamo è un'azione IO con un risultato di tipo () che non fa nulla.

Ebbene, lo scopo di return è di costruire un'azione che non ha effetti, e solo restituisce un valore costante, e return() ha il tipo giusto per questo: IO() (o più in generale, (Monad m) => m()). Quindi ??? è return() (che puoi vedere ho usato nel mio snippet migliorato sopra, per non fare nulla quando removeFile fallisce perché il file non esiste).

(A proposito, si dovrebbe ora essere in grado di implementare when con l'aiuto di return(): è molto semplice :))

Non ti preoccupare se si fatica a entrare nel modo Haskell delle cose all'inizio - arriverà naturalmente nel tempo e, quando lo farà, sarà molto gratificante. :)

+4

Ecco un collegamento alla documentazione di Control.Exception: http://hackage.haskell.org/packages/archive/base/latest/doc/html/Control-Exception.html - Non potrei inserirlo nel post perché non lo faccio Non ho abbastanza reputazione:/ – ehird

+2

Grazie, è stato davvero utile! – yogsototh

+1

Nessun problema: per quanto riguarda il materiale di lettura, c'è l'eccellente [Learn You a Haskell] (http://learnyouahaskell.com/) e [Real World Haskell] (http://book.realworldhaskell.org/), ma Immagino che tu sappia già di questi. Posso consigliare l'abilità [Hoogle] (http://www.haskell.org/hoogle/) di cercare per tipo inestimabile per la programmazione Haskell. – ehird

10

(Nota: la risposta di ehird rende un ottimo punto per quanto riguarda una condizione di competizione Va tenuto a mente quando leggendo la mia risposta, che ignora il. Si noti inoltre che lo pseudo-codice imperativo presentato nella domanda soffre anche dello stesso problema.)

Cosa definisce il nome file? Viene fornito nel programma o fornito dall'utente? Nel tuo pseudo-codice imperativo, è una stringa costante nel programma. Immagino che tu voglia che l'utente lo fornisca passandolo come il primo argomento della riga di comando al programma.

allora vi consiglio qualcosa di simile:

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 

(Come potete vedere, ho aggiunto la firma tipo di doSomethingElse per evitare confusione).

Importazione System.Environment per la funzione getArgs. Nel caso in cui il file in questione sia semplicemente dato da una stringa costante (come nel tuo pseudo-codice imperativo), basta rimuovere tutta la roba di args e compilare la stringa costante ovunque io abbia head args.

Control.Monad viene importato per ottenere la funzione when. Notare che questa funzione utile è non una parola chiave (come if), ma una funzione normale. Diamo un'occhiata a suo tipo:

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

Nel tuo caso m è IO, in modo da poter pensare a when come una funzione che prende un Bool e un'azione IO ed esegue l'azione solo se il Bool è True. Ovviamente è possibile risolvere il problema con if s, ma nel tuo caso when legge molto più chiaro. Almeno credo.

Addendum: Se, come ho fatto io in un primo momento, la sensazione che when è alcuni macchinari magica e difficile, è molto istruttivo per cercare di definire la funzione da soli. Te lo prometto, è morto semplice ...

+4

È anche istruttivo pensare a * perché * è semplice definire "quando" e quali altre implicazioni ha. I blocchi di codice imperativo sono entità di prima classe in Haskell in un modo che poche lingue possono eguagliare. –

Problemi correlati