一次在文件系统上执行多个操作的正确方法是什么?

时间:2018-02-09 05:43:40

标签: haskell

假设我想知道文件是否存在,如果它是一个目录,还要检索其内容。我可以如下:

browseSimple :: FilePath -> IO (Either FilePath [FilePath])
browseSimple x = do
    isAvailable <- doesPathExist x
    if not isAvailable then error $ "File not found: " ++ x else do
        isFile <- doesFileExist x
        if isFile then return $ Left x else do
            isDirectory <- doesDirectoryExist x
            if not isDirectory then error $ "Unknown filesystem node: " ++ x else do
                listing <- listDirectory x
                return $ Right ((x </>) <$> listing)
-- ^
-- λ browseSimple  "."
-- Right [..."./Filesystem.hs"...]

- 它有点有效。但是我想知道:如果节点在路上被取消链接会发生什么,比如在最后一个“do”块之前呢?

我不知道C,但我的猜测是所有这些iffiness将被1(一)POSIX系统调用取代:opendir将让我读取目录内容或返回一个有意义的错误我可以模式匹配。但这只适用于符合POSIX标准的系统。

在Haskell中执行此类操作的正确,惯用,专业方式是什么?我是否使用System.Posix.Files中的内容解决了这个问题?这周围的最新技术是什么?

postscriptum

我本来可以投放listDirectory,并在错误上匹配模式(根据@Ryan的建议),但我有点怀疑,因为如果两者{NoSuchThing显然可以说ENOENT {1}}和ENOTDIR。描述很少,行为没有拼写出来,我不想读任何保证。

1 个答案:

答案 0 :(得分:5)

  

但是我想知道:如果节点在路上被取消链接会发生什么,比如在最后一个“do”块之前呢?

你会得到一个例外,在这种情况下这没什么大不了的。您正在执行IO并且可以处理它。我认为你不会找到一个防故障平台无关的解决方案。

如何制作一个有用的包装器monad来保持代码的可读性?它可能比你喜欢的样板(在这种情况下只用catch包装整个函数调用)但是对于更大部分的代码可能非常好:

data MyErr = CaughtException SomeException
                             -- ^ You might want to just catch
                             --   specific types of exceptions here
          | MyErr String

type M = ExceptT MyErr IO

myErr :: String -> M a
myErr = throwE . MyErr

myIO :: IO a -> M a
myIO io = ExceptT (catch (Right <$> io) (pure . Left . CaughtException))

M monad允许您捕获所有丑陋的异常以及程序逻辑,并将其捆绑到单个Either结果中。你可以这样使用它:

browseSimple :: FilePath -> IO (Either MyErr [FilePath])
browseSimple x = runExceptT $ do
    isAvailable <- myIO $ doesPathExist x
    when (not isAvailable) (myErr $ "File not found: " ++ x)
    isFile <- myIO $ doesFileExist x
    when isFile (myErr x)
    isDirectory <- myIO $ doesDirectoryExist x
    when (not isDirectory) (myErr $ "Unknown filesystem node: " ++ x)
    listing <- myIO $ listDirectory x
    return ((x </>) <$> listing)

你可以增强一些东西,比如提供myIO更多的信息,所以如果/当事情失败你可以将结果绑定到你的操作中的东西,但这通常是过度的。

我们可以使用whenM(键入未测试)进一步清理:

whenM io m = myIO io >>= \b -> when b m

browseSimple2 :: FilePath -> IO (Either MyErr [FilePath])
browseSimple2 x = runExceptT $ do
    whenM (not <$> doesPathExist x)
          (myErr $ "File not found: " ++ x)
    whenM (doesFileExist x)
          (myErr x)
    whenM (not <$> doesDirectoryExist x)
          (myErr $ "Unknown filesystem node: " ++ x)
    myIO $ (x </>) <$> listDirectory x