A inovação mais aparente observada por pessoas novas em Haskell é que existe uma separação entre o mundo impuro que se preocupa em se comunicar com o mundo exterior e o mundo puro da computação e algoritmos. Uma pergunta freqüente para iniciantes é "Como posso me livrar IO
, ou seja, converter IO a
em a
?" O caminho para isso é usar mônadas (ou outras abstrações) para escrever código que execute efeitos de IO e encadeamentos. Esse código reúne dados do mundo exterior, cria um modelo, faz algumas computações, possivelmente empregando código puro, e gera o resultado.
No que diz respeito ao modelo acima, não vejo nada de errado em manipular GUIs na IO
mônada. O maior problema que surge desse estilo é que os módulos não são mais compostáveis, ou seja, perco a maior parte do meu conhecimento sobre a ordem de execução global das instruções no meu programa. Para recuperá-lo, tenho de aplicar um raciocínio semelhante ao do código da GUI concorrente e imperativo. Enquanto isso, para um código não-GUI impuro, a ordem de execução é óbvia por causa da definição IO
do >==
operador da mônada (pelo menos enquanto houver apenas um encadeamento). Para código puro, isso não importa, exceto em casos extremos para aumentar o desempenho ou evitar avaliações resultantes ⊥
.
A maior diferença filosófica entre console e E / S gráfica é que os programas que implementam o primeiro são geralmente escritos em estilo síncrono. Isso é possível porque existe (deixando de lado os sinais e outros descritores de arquivos abertos) apenas uma fonte de eventos: o fluxo de bytes normalmente chamado stdin
. As GUIs são inerentemente assíncronas e precisam reagir a eventos do teclado e cliques do mouse.
Uma filosofia popular de executar IO assíncrona de uma maneira funcional é chamada de Programação Reativa Funcional (FRP). Recentemente, houve muita tração em linguagens impuras e não funcionais, graças a bibliotecas como o ReactiveX e estruturas como o Elm. Em poucas palavras, é como visualizar elementos da GUI e outras coisas (como arquivos, relógios, alarmes, teclado, mouse) como fontes de eventos, chamadas "observáveis", que emitem fluxos de eventos. Estes eventos são combinados usando operadores conhecidos, como map
, foldl
, zip
, filter
, concat
, join
, etc., para produzir novos fluxos. Isso é útil porque o próprio estado do programa pode ser visto como scanl . map reactToEvents $ zipN <eventStreams>
do programa, onde N
é igual ao número de observáveis já considerados pelo programa.
Trabalhar com observáveis de FRP torna possível recuperar a composição porque os eventos em um fluxo são ordenados a tempo. O motivo é que a abstração do fluxo de eventos torna possível visualizar todos os observáveis como caixas pretas. Por fim, a combinação de fluxos de eventos usando operadores devolve algumas ordens locais na execução. Isso me obriga a ser muito mais honesto em relação aos invariantes em que meu programa realmente se baseia, semelhante à maneira como todas as funções no Haskell precisam ser referencialmente transparentes: se eu quiser extrair dados de outra parte do meu programa, preciso ser explícito. O anúncio declara um tipo apropriado para minhas funções. (A mônada de IO, sendo uma linguagem específica de domínio para escrever código impuro, evita isso efetivamente)