【问题标题】:Remove file if it exists删除文件(如果存在)
【发布时间】:2015-08-23 22:25:44
【问题描述】:

在 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

【问题讨论】:

  • doesFileExist 函数实际上是对竞争条件的邀请。它不应该存在。
  • @augustss:我们把它重命名为didFileExistLastTimeIChecked怎么样?
  • 我建议didFileNotNeverExist

标签: haskell


【解决方案1】:

如果文件不存在,您最好删除文件并简单地恢复:

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) 行——这是因为 Prelude 包含来自异常处理的旧函数,现在已弃用这些函数,取而代之的是 Control.Exception,它还有一个名为 catch 的函数;导入行只是隐藏了 Prelude 的 catch 以支持 Control.Exception

但是,这仍然留下了您更基本的潜在问题:您如何在IO 中编写条件语句?

好吧,在这种情况下,只需这样做就足够了

when fileExists $ removeFile filename

(使用Control.Monad.when)。但是在这里查看类型很有帮助,就像在 Haskell 中通常一样。

条件的两个分支必须具有相同的类型。所以要填写

if fileExists
    then removeFile filename
    else ???

我们应该看看removeFile filename的类型;无论??? 是什么,它都必须具有相同的类型。

System.Directory.removeFile 的类型为 FilePath -&gt; IO (),因此 removeFile filename 的类型为 IO ()。所以我们想要的是一个 IO 操作,其结果类型为 (),它什么都不做。

好吧,return 的目的是构造一个没有效果的动作,只返回一个常量值,而return () 具有正确的类型:IO ()(或更一般地说,(Monad m) =&gt; m () )。所以???return ()(你可以看到我在上面改进的sn-p 中使用了它,当removeFile 因为文件不存在而失败时什么都不做)。

(顺便说一句,您现在应该可以在return () 的帮助下实现when;这真的很简单:))

如果您一开始很难进入 Haskell 的工作方式,请不要担心 - 它会及时自然而然地出现,当它进入时,它会非常有益。 :)

【讨论】:

  • 这里是 Control.Exception 文档的链接:hackage.haskell.org/packages/archive/base/latest/doc/html/… — 我不能把它放在帖子里,因为我没有足够的声誉:/
  • 谢谢,这真的很有帮助!
  • 没问题——就阅读材料而言,有优秀的Learn You a HaskellReal World Haskell,但我想你已经知道这些了。不过,我可以推荐 Hoogle 按类型搜索的能力对于 Haskell 编程来说是无价的。
  • 我把链接放到正文里了。不错的答案,将帮助您获得足够的代表来自己添加两个以上的链接:)
  • 欢迎来到 SO!很高兴在这里见到你。你现在已经超过 100 个代表了,所以大部分真正令人恼火的限制都应该消失了。 :]
【解决方案2】:

(注意: 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的类型签名以避免混淆)。

我为getArgs 函数导入System.Environment。如果有问题的文件只是由常量字符串给出(例如在您的命令式伪代码中),只需删除所有 args 内容并在我有 head args 的地方填写常量字符串。

Control.Monad 被导入以获得when 函数。请注意,这个有用的函数不是关键字(如if),而是一个普通函数。我们来看看它的类型:

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

在您的情况下mIO,因此您可以将when 视为一个接受Bool 和IO 操作的函数,并且仅当BoolTrue 时才执行该操作。当然你可以用ifs 来解决你的问题,但在你的情况下when 读起来更清楚。至少我是这么认为的。

附录:如果您像我一开始一样,觉得when 是一些神奇而困难的机器,那么尝试自己定义函数非常有启发性。我向你保证,这很简单......

【讨论】:

  • 思考为什么定义when 很简单,以及它还有什么其他含义也很有启发意义。命令式代码块是 Haskell 中的一流实体,几乎没有语言可以匹配。
猜你喜欢
  • 1970-01-01
  • 2013-04-16
  • 2015-02-04
  • 2016-03-01
  • 1970-01-01
  • 1970-01-01
  • 2015-07-12
  • 1970-01-01
  • 2015-10-31
相关资源
最近更新 更多