haskell - ¿Cómo trato con muchos niveles de sangría?
indentation code-organization (3)
Debería hacer lo mismo que hubiera hecho con cualquier otro lenguaje de programación. Las funciones deben ser fáciles de entender. Esto generalmente significa que si es largo no hay mucho flujo de control, de lo contrario se divide en funciones separadas.
Entonces main podría verse así:
main = do
inFH <- openFile ...
outFH <- openFile ....
mapM prcoessItem myList
Estoy escribiendo un script que tiene un ciclo muy lógicamente complicado:
main = do
inFH <- openFile "..." ReadMode
outFH <- openFile "..." WriteMode
forM myList $ / item ->
...
if ...
then ...
else do
...
case ... of
Nothing -> ...
Just x -> do
...
...
El código pronto vuela a la derecha, así que estaba pensando en romperlo en pedazos, usando, por ejemplo, las cláusulas
where
.
El problema es que muchos de estos
...
contienen declaraciones de lectura / escritura para los dos identificadores
inFH
y
outFH
, y el uso de una instrucción
where
hará que esos dos nombres estén fuera de contexto.
Tendría que enviar estas dos variables cada vez que use una instrucción
where
.
¿Hay una mejor manera de lidiar con esto?
En muchos casos, estas muescas profundamente anidadas son el resultado de una verificación de errores profundamente anidada.
Si es así para ti, deberías buscar en
MaybeT
y su hermano mayor
ExceptT
.
Estos ofrecen una manera limpia de separar el código de "qué hacemos cuando algo salió mal" del código de "qué hacemos suponiendo que todo salga bien".
En su ejemplo, podría escribir:
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
Observe que todavía aumentamos la sangría en el bucle
forM
;
pero las otras verificaciones se realizan "en línea" en
main
, y se manejan todas al mismo nivel de sangría en
handleErrors
.
Si bien es probable que haya mejores formas de resolver su problema concreto (consulte, por ejemplo, la respuesta de Daniel Wagner), siempre puede usar
let
para introducir un nuevo nombre dentro de un ámbito arbitrario.
Aquí hay una demostración sin sentido:
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