What is the right way of doing this in Haskell?
if exists \"foo.txt\" then delete \"foo.txt\"
doSomethingElse
So far I have:
im
You would be better off removing the file and simply recovering if it does not exist:
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
This avoids the race condition of someone deleting the file between your code checking whether it exists and deletes it. It might not matter in your case, but it's good practice anyway.
Note the import Prelude hiding (catch)
line — this is because the Prelude contains older functions from exception handling which are now deprecated in favour of Control.Exception, which also has a function named catch
; the import line simply hides the Prelude's catch
in favour of Control.Exception's.
However, that still leaves your more fundamental underlying question: how do you write conditionals in IO
?
Well, in this case, it would suffice to simply do
when fileExists $ removeFile filename
(using Control.Monad.when). But it's helpful here, as it usually is in Haskell, to look at the types.
Both branches of a conditional must have the same type. So to fill in
if fileExists
then removeFile filename
else ???
we should look at the type of removeFile filename
; whatever ???
is, it has to have the same type.
System.Directory.removeFile has the type FilePath -> IO ()
, so removeFile filename
has the type IO ()
. So what we want is an IO action with a result of type ()
that does nothing.
Well, the purpose of return
is to construct an action that has no effects, and just returns a constant value, and return ()
has the right type for this: IO ()
(or more generally, (Monad m) => m ()
). So ???
is return ()
(which you can see I used in my improved snippet above, to do nothing when removeFile
fails because the file doesn't exist).
(By the way, you should now be able to implement when
with the help of return ()
; it's really simple :))
Don't worry if you find it hard to get into the Haskell way of things at first — it'll come naturally in time, and when it does, it's very rewarding. :)
(Note: ehird's answer makes a very good point regarding a race condition. It should be kept in mind when reading my answer, which ignores the issue. Do note also that the imperative pseudo-code presented in the question also suffers from the same problem.)
What defines the filename? Is it given in the program, or supplied by the user? In your imperative pseudo-code, it's a constant string in the program. I'll assume you want the user to supply it by passing it as the first command line argument to the program.
Then I suggest something like this:
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
(As you can see, I added the type signature of doSomethingElse
to avoid confusion).
I import System.Environment
for the getArgs
function. In case the file in question is simply given by a constant string (such as in your imperative pseudo-code), just remove all the args stuff and fill in the constant string wherever I have head args
.
Control.Monad
is imported to get the when
function. Note that this useful function is not a keyword (like if
), but an ordinary function. Let's look at its type:
when :: Monad m => Bool -> m () -> m ()
In your case m
is IO
, so you can think of when
as a function that takes a Bool
and an IO action and performs the action only if the Bool
is True
. Of course you could solve your problem with if
s, but in your case when
reads a lot clearer. At least I think so.
Addendum: If you, like I did at first, get the feeling that when
is some magical and difficult machinery, it's very instructive to try to define the function yourself. I promise you, it's dead simple...