Esse é um padrão de design válido para uma função principal do Haskell?

8

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 mainestrutura de alto nível para impor essa segregação.

Em geral, meu mainterá 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?

recursion.ninja
fonte

Respostas:

7

Esse design faz várias suposições não triviais:

  • A entrada do usuário não dependerá dos resultados de cálculos puros ou impuros.

  • Os cálculos impuros não dependerão do resultado de cálculos puros.

  • A lógica do programa não fará um loop; ele será executado apenas uma vez.

Meu outro comentário sobre sua estrutura é que você não precisa separar os cálculos puros e impuros. O sistema de tipos da Haskell já faz isso por você.

Dito isto, essa estrutura certamente parece útil para determinadas classes de programas, especialmente se você tiver certeza de que as suposições descritas acima são verdadeiras para o seu programa. Porém, não é algo que todo programa deva usar.

WolfeFan
fonte
Além disso, se você deseja simplificar seu padrão, considere que andGetUserInput quase certamente conta como uma ImpureOrFalliableComputation. Você provavelmente pode combiná-los em uma seção.
WolfeFan 18/09/2014
Na prática, eles são condensados; para demonstração e exposição, eu os separei.
recursion.ninja