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

Ign*_*rov 2 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"...]
Run Code Online (Sandbox Code Playgroud)

- 它有点工作.但我想知道:如果节点在路上被取消链接会发生什么,比如说在最后一个"do"块之前?

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

在Haskell中做这样的事情的正确,惯用,专业的方法是什么?我用这些东西解决它System.Posix.Files吗?这周围的最新技术是什么?


postscriptum

我可以刚刚listDirectory投入,并且模式匹配错误(根据@Ryan的建议),但我有点怀疑,因为它可以显然说NoSuchThing两种情况ENOENTENOTDIR.描述很少,行为没有拼写出来,我不想读任何保证.

Tho*_*son 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))
Run Code Online (Sandbox Code Playgroud)

M单子让你捕获所有的丑陋异常与程序逻辑一起捆绑并卷到一个单一的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)
Run Code Online (Sandbox Code Playgroud)

你可以增强一些东西,例如提供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
Run Code Online (Sandbox Code Playgroud)