2011-12-14 33 views
24

¿Cuál es la forma correcta de hacerlo en Haskell?Eliminar archivo si existe

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

hasta ahora tengo:

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

La función 'doesFileExist' es realmente una invitación a las condiciones de carrera. No debería existir. – augustss

+7

@augustss: ¿Qué tal si cambiamos el nombre a 'didFileExistLastTimeIChecked'? –

+4

Sugiero 'didFileNotNeverExist'. – ehird

Respuesta

44

Usted sería mejor eliminar el archivo y simplemente recuperar si no existe:

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 

Esto evita la condición de carrera de una persona borrar el archivo entre su código que comprueba si existe y lo elimina. Puede que no importe en su caso, pero de todos modos es una buena práctica.

Observe la línea import Prelude hiding (catch) - esto se debe a que el Preludio contiene funciones más antiguas del manejo de excepciones que ahora están en desuso en favor de Control.Exception, que también tiene una función llamada catch; la línea de importación simplemente oculta el Prelude's catch a favor de Control.Exception.

Sin embargo, eso aún deja su pregunta subyacente más fundamental: ¿cómo se escribe condicional en IO?

Pues bien, en este caso, bastaría con que se haga

when fileExists $ removeFile filename 

(usando Control.Monad.when). Pero es útil aquí, como suele ser en Haskell, mirar los tipos.

Ambas ramas de un condicional deben tener el mismo tipo. Así que rellenar

if fileExists 
    then removeFile filename 
    else ??? 

debemos mirar el tipo de removeFile filename; cualquiera que sea ??? es, tiene que tener el mismo tipo.

System.Directory.removeFile tiene el tipo FilePath -> IO(), entonces removeFile filename tiene el tipo IO(). Entonces, lo que queremos es una acción IO con un resultado del tipo () que no hace nada.

Bueno, el propósito de return es construir una acción que no tiene efectos, y sólo devuelve un valor constante, y return() tiene el tipo correcto para esto: IO() (o más generalmente, (Monad m) => m()). Por lo tanto, ??? es return() (que puede ver que usé en mi fragmento mejorado anterior, para no hacer nada cuando removeFile falla porque el archivo no existe).

(Por cierto, ahora debería ser capaz de implementar when con la ayuda de return(), es muy simple :))

No se preocupe si le resulta difícil entrar en el camino de las cosas Haskell al principio, llegará naturalmente a tiempo, y cuando lo haga, será muy gratificante. :)

+4

Aquí hay un enlace a la documentación de Control.Exception: http://hackage.haskell.org/packages/archive/base/latest/doc/html/Control-Exception.html - No pude ponerlo en la publicación porque no No tengo suficiente reputación:/ – ehird

+2

¡Gracias, fue realmente útil! – yogsototh

+1

No hay problema: en lo que respecta al material de lectura, está el excelente [Aprende Haskell] (http://learnyouahaskell.com/) y [Real World Haskell] (http://book.realworldhaskell.org/), pero Me imagino que ya sabes sobre esto. Sin embargo, puedo recomendar la habilidad de [Hoogle] (http://www.haskell.org/hoogle/) para buscar por tipo, algo muy valioso para la programación de Haskell. – ehird

10

(Nota: Respuesta de ehird hace un muy buen punto con respecto a una condición de carrera Se debe tener en cuenta al leer mi respuesta, que ignora el. También tenga en cuenta que el pseudocódigo imperativo presentado en la pregunta también adolece del mismo problema.)

¿Qué define el nombre de archivo? ¿Se da en el programa o es suministrado por el usuario? En su pseudocódigo imperativo, es una cadena constante en el programa. Asumiré que desea que el usuario lo suministre pasándolo como el primer argumento de línea de comando para el programa.

Entonces me sugieren algo como esto:

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 

(Como se puede ver, he añadido la firma tipo de doSomethingElse para evitar confusiones).

Importe System.Environment para la función getArgs. En caso de que el archivo en cuestión simplemente esté dado por una cadena constante (como en su pseudocódigo imperativo), simplemente elimine todas las cosas de args y complete la cadena constante donde sea que tenga head args.

Control.Monad se importa para obtener la función when. Tenga en cuenta que esta función útil es no una palabra clave (como if), sino una función normal. Veamos su tipo:

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

En su caso m es IO, de modo que se pueda imaginar when como una función que toma un Bool y una acción IO y realiza la acción sólo si el Bool es True. Por supuesto, usted podría resolver su problema con if s, pero en su caso when lee mucho más claro. Al menos eso pienso.

Adición: Si, como lo hice en un primer momento, la sensación de que es un poco de maquinaria when mágico y difícil, es muy instructivo para tratar de definir la función de sí mismo. Te prometo, que está muerto sencillo ...

+4

También es instructivo pensar sobre * por qué * es simple definir 'cuándo', y qué otras implicaciones tiene. Los bloques de código imperativo son entidades de primera clase en Haskell de una manera que pocos idiomas pueden igualar. –

Cuestiones relacionadas