如何处理多级缩进?

发布于 2025-01-17 14:00:39 字数 679 浏览 1 评论 0原文

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

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 语句时,我都必须发送这两个变量。

有更好的方法来处理这个问题吗?

I am writing a script that has a very logically complicated loop:

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

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

The code soon flies to the right, so I was thinking breaking it into pieces, using for example where clauses. The problem is, many of these ... contain reading/writing statements to the two handles inFH and outFH, and using a where statement will render those two names out of context. I would have to send in these two variables everytime I use a where statement.

Is there a better way of dealing with this?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(3

回忆躺在深渊里 2025-01-24 14:00:39

在许多情况下,这些深层嵌套的缩进是深层嵌套的错误检查的结果。如果您也是这样,那么您应该研究一下 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 中以相同的缩进级别进行处理。

In many cases, these deeply-nested indentations are the result of deeply-nested error checking. If that's so for you, you should look into MaybeT and its big brother ExceptT. These offer a clean way to separate the "what do we do when something went wrong" code from the "what do we do assuming everything goes right" code. In your example, I might write:

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

Notice that we still increase indentation at the forM loop; but the other checks are done "in-line" in main, and are handled all at the same indentation level in handleErrors.

怀里藏娇 2025-01-24 14:00:39

虽然有更好的方法可以解决您的具体问题(请参阅eg 丹尼尔·瓦格纳(Daniel Wagner)的答案),您可以始终使用>让在任意范围内引入新名称。这是一个公认的毫无意义的演示:

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

While there likely are nicer ways to solve your concrete problem (see e.g. Daniel Wagner's answer), you can always use let to introduce a new name within an arbitrary scope. Here is an admittedly nonsensical demo:

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
回首观望 2025-01-24 14:00:39

您应该使用任何其他编程语言做同样的事情。功能应该易于理解。这通常意味着,如果它很长,则没有太多的控制流,否则将其分为单独的功能。

因此,主要可能看起来像:

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

    mapM prcoessItem myList

You should do the same thing you would have done with any other programming language. Functions should be easy to understand. This typically means that if it is long there isn't a lot of control flow, otherwise split it up in to separate functions.

So main might look like:

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

    mapM prcoessItem myList
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文