haskell indentation code-organization

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