How do I deal with many levels of indentation?

泪湿孤枕 提交于 2019-11-26 06:08:43

问题


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?


回答1:


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.




回答2:


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



回答3:


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


来源:https://stackoverflow.com/questions/33005903/how-do-i-deal-with-many-levels-of-indentation

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!