¿Es este un patrón de diseño válido para una función principal de Haskell?

8

Después de desarrollar varias aplicaciones de Haskell, me encontré segregando rigurosamente el código impuro y las funciones de fallas ( parciales ) de sus contrapartes puras y totales . Estos esfuerzos han reducido notablemente el costo de mantenimiento asociado con las aplicaciones. Con el tiempo me he encontrado confiando en la misma mainestructura de alto nivel para hacer cumplir esta segregación.

En general, mi maintendrá la siguiente estructura:

import System.Environment

data ProgramParameters = P ()
data ComputationResult = I ()

main :: IO ()
main = getArgs                           -- Collect arguments
   >>= andOrGetUserInput                 -- Collect user input
   >>= impureOrFailableComputations      -- Possible non-recoverable error(s)
   >>= either                            -- "Branch"
         putStrLn                        -- Print Any Failure(s)
         pureNotFailableComputations     -- Finish the work

andOrGetUserInput :: [String] -> IO ProgramParameters
andOrGetUserInput = undefined

impureOrFailableComputations :: ProgramParameters -> IO (Either String ComputationResult)
impureOrFailableComputations = undefined -- a composition of partial functions
                                         -- made total by catching exceptions & input errors
                                         -- in the short-circuiting ErrorT/EitherT monad

pureNotFailableComputations :: ComputationResult -> IO ()
pureNotFailableComputations = undefined  -- a composition of total functions

El objetivo es unir cálculos parciales en una mónada, creando un cálculo monádico total.

Esto se ha convertido en un patrón en la base del código, y me gustaría recibir comentarios sobre si se trata de un patrón de diseño o un antipatrón .

  • ¿Es esta una forma idiomática de segregar y atrapar cálculos parciales?

  • ¿Hay inconvenientes notables en esta segregación de alto nivel?

  • ¿Hay mejores técnicas de abstracción?

recursion.ninja
fuente

Respuestas:

7

Este diseño hace varias suposiciones no triviales:

  • La entrada del usuario no dependerá de los resultados de cálculos puros o impuros.

  • Los cálculos impuros no dependerán del resultado de cálculos puros.

  • La lógica del programa no se repetirá; correrá solo una vez.

Mi otro comentario sobre su estructura es que no necesita separar los cálculos puros e impuros. El sistema de tipos de Haskell ya lo hace por ti.

Dicho esto, esta estructura ciertamente parece útil para ciertas clases de programas, especialmente si está seguro de que los supuestos que describí anteriormente en realidad son válidos para su programa. Sin embargo, no es algo que todo programa deba usar.

WolfeFan
fuente
Además, si desea simplificar su patrón, tenga en cuenta que andOrGetUserInput casi seguramente cuenta como una impureOrFalliableComputation. Probablemente puedas combinarlos en una sola sección.
WolfeFan
En la práctica, están condensados; para demostración y exposición, los separé.
recursion.ninja