Depois de desenvolver vários aplicativos Haskell, me vi segregando rigorosamente código impuro e funções disponíveis ( parciais ) de seus equivalentes puros e totais . Esses esforços reduziram notavelmente o custo de manutenção associado aos aplicativos. Ao longo do tempo, me vi confiando na mesma main
estrutura de alto nível para impor essa segregação.
Em geral, meu main
terá a seguinte estrutura:
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
O objetivo é unir cálculos parciais em uma mônada, criando um cálculo monádico total.
Isso se tornou um padrão na base de código, e eu gostaria de receber um feedback sobre se esse é um padrão de design ou um antipadrão .
Essa é uma maneira idiomática de segregar e capturar cálculos parciais?
Existem desvantagens notáveis nessa segregação de alto nível?
Existem melhores técnicas de abstração?