如何处理多级缩进?

5

我正在编写一个逻辑非常复杂的循环脚本:

main = do
    inFH <- openFile "..." ReadMode
    outFH <- openFile "..." WriteMode

    forM myList $ \ item ->
        ...
        if ... 
            then ...
            else do
                ...
                case ... of
                    Nothing -> ...
                    Just x  -> do
                        ...
                            ...

代码很快就会向右飞,因此我考虑将其分成几个部分,例如使用where子句。问题是,许多这些...包含读取/写入语句到两个句柄inFHoutFH,使用where语句会使这两个名称失去上下文。每次使用where语句时,我都必须发送这两个变量。

有更好的处理方法吗?

3个回答

10

在许多情况下,这些深度嵌套的缩进是深度嵌套错误检查的结果。如果您的情况是如此,您应该研究 MaybeT 和它的大兄弟 ExceptT。它们提供了一种清晰的方法来将“当出现问题时我们该怎么做”代码与“假设一切正常我们该怎么做”代码分开。在您的示例中,我可能会写成:

data CustomError = IfCheckFailed | MaybeCheckFailed

main = handleErrors <=< runExceptT $ do
    inFH  <- liftIO $ openFile ...
    outFH <- liftIO $ openFile ...
    forM myList $ \item -> do
        when (...) (throwError IfCheckFailed)
        ...
        x <- liftMaybe MaybeCheckFailed ...
        ...

liftMaybe :: MonadError e m => e -> Maybe a -> m a
liftMaybe err = maybe (throwError err) return

handleErrors :: Either CustomError a -> IO a
handleErrors (Left err) = case err of
    IfCheckFailed    -> ...
    MaybeCheckFailed -> ...
handleErrors (Right success) = return success

请注意我们仍然在 forM 循环中增加缩进;但是其他检查都是在 main 中“内联”完成的,并且在 handleErrors 中以相同的缩进级别处理所有检查。


6

虽然您解决具体问题的方式可能还有更好的方法(例如请参考Daniel Wagner的回答),但是您始终可以使用let在任意作用域内引入一个新名称。这里是一个明显毫无意义的演示:

main = do
    inFH <- return "inf"
    outFH <- return "ouf"

    let subAction = do
            if length inFH > 2
                then print "foo"
                else subSubAction

        subSubAction = case outFH of
            [] -> print "bar"
            _ -> print "baz"

    forM [1..10] $ \ item -> do
        print item
        subAction

谢谢。这是我忽略的一点。然而,我不喜欢在这里使用let的原因之一是它翻转了阅读顺序——我首先定义步骤的一部分,然后才是其他部分的流程。另外,我也不喜欢给逻辑流程的一部分命名,这也是我想避免使用where的另一个原因。 - xzhu

2

你应该像处理其他编程语言一样处理。函数应该易于理解。通常意味着,如果函数很长,那么控制流程就不会很复杂,否则需要将其拆分为单独的函数。

所以主函数可能如下所示:

main = do
    inFH <- openFile ...
    outFH <- openFile ....

    mapM prcoessItem myList

嗯,这基本上与不传递“inFH”和“outFH”的要求不兼容。 - Ørjan Johansen

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接