如何处理多级缩进?
How do I deal with many levels of indentation?
我正在编写一个逻辑上非常复杂的循环的脚本:
main = do
inFH <- openFile "..." ReadMode
outFH <- openFile "..." WriteMode
forM myList $ \ item ->
...
if ...
then ...
else do
...
case ... of
Nothing -> ...
Just x -> do
...
...
代码很快就向右飞了,所以我想把它分成几部分,例如使用 where
子句。问题是,其中许多 ...
包含对两个句柄 inFH
和 outFH
的 reading/writing 语句,使用 where
语句会将这两个名称渲染出来上下文。每次使用 where
语句时,我都必须发送这两个变量。
有没有更好的方法来处理这个问题?
您应该做与使用任何其他编程语言相同的事情。功能应该易于理解。这通常意味着如果它很长,则没有很多控制流,否则将其拆分为单独的功能。
所以 main 可能看起来像:
main = do
inFH <- openFile ...
outFH <- openFile ....
mapM prcoessItem myList
在很多情况下,这些嵌套很深的缩进是嵌套很深的错误检查的结果。如果这对你来说是这样,你应该看看 MaybeT
和它的老大哥 ExceptT
。这些提供了一种将 "what do we do when something went wrong" 代码与 "what do we do assuming everything goes right" 代码分开的简洁方法。在你的例子中,我可能会写:
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
中完成 "in-line",并在 handleErrors
.
中以相同的缩进级别处理
虽然可能有更好的方法来解决您的具体问题(参见 ),但您始终可以使用 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
我正在编写一个逻辑上非常复杂的循环的脚本:
main = do
inFH <- openFile "..." ReadMode
outFH <- openFile "..." WriteMode
forM myList $ \ item ->
...
if ...
then ...
else do
...
case ... of
Nothing -> ...
Just x -> do
...
...
代码很快就向右飞了,所以我想把它分成几部分,例如使用 where
子句。问题是,其中许多 ...
包含对两个句柄 inFH
和 outFH
的 reading/writing 语句,使用 where
语句会将这两个名称渲染出来上下文。每次使用 where
语句时,我都必须发送这两个变量。
有没有更好的方法来处理这个问题?
您应该做与使用任何其他编程语言相同的事情。功能应该易于理解。这通常意味着如果它很长,则没有很多控制流,否则将其拆分为单独的功能。
所以 main 可能看起来像:
main = do
inFH <- openFile ...
outFH <- openFile ....
mapM prcoessItem myList
在很多情况下,这些嵌套很深的缩进是嵌套很深的错误检查的结果。如果这对你来说是这样,你应该看看 MaybeT
和它的老大哥 ExceptT
。这些提供了一种将 "what do we do when something went wrong" 代码与 "what do we do assuming everything goes right" 代码分开的简洁方法。在你的例子中,我可能会写:
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
中完成 "in-line",并在 handleErrors
.
虽然可能有更好的方法来解决您的具体问题(参见 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