How can I handle many levels of indentation?

I am writing a script that has a very logically complex 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 thought of breaking it into pieces using, for example, where clauses. The problem is that many of these ... contain read / write instructions for the two inFH and outFH , and using the where operator will cause these two names to be excluded from the context. I would have to send these two variables every time I use the where statement.

Is there a better way to handle this?

+4
source share
3 answers

In many cases, these deeply nested indents are the result of deeply nested error checking. If so, you should take a peek at MaybeT and his older brother ExceptT . They offer a clean way to separate the code "what do we do when something went wrong" from the code "what do we do if everything goes right." In your example, I can 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 em => e -> Maybe a -> ma 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 

Note that we are still increasing the indentation in the forM loop; but other checks are performed "in-line" in main and processed at the same level of indentation in handleErrors .

+9
source

While there are probably more efficient ways to solve your specific problem (see, for example, Daniel Wagner's answer), you can always use let to enter a new name in an arbitrary area. Here's an admittedly pointless demonstration:

 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 
+5
source

You must do the same as with any other programming language. The functions should be clear. This usually means that if it is long, the control flow is not too much; otherwise, divide it into separate functions.

Thus, the main thing may look like this:

 main = do inFH <- openFile ... outFH <- openFile .... mapM prcoessItem myList 
+2
source

All Articles