coldwa.st
Todos os guiasProgramaçãoWebDadosFerramentasBases de dadosHaskellConceitosCabal e buildsToolchainCompiladorDesempenhoEditor e HLS

Haskell · conceitos · monades

As monades em Haskell, explicadas sem o jargão

Por ColdwastAtualizado a 14 de junho de 20268 min de leitura#haskell#monads#concepts
Código-fonte num ecrã escuro
Código-fonte num ecrã escuro — as monades são um padrão que se escreve diretamente em código como este.

«Monade» tem uma reputação assustadora que não merece. Despida do jargão, uma monade em Haskell é simplesmente um tipo que lhe permite sequenciar cálculos que transportam um certo contexto — uma possível falha, um efeito secundário, vários resultados — sem escrever a canalização à mão de cada vez. Este guia explica o que é realmente uma monade, as duas operações que a definem, a do-notation e as monades do dia a dia que já utiliza.

A ideia numa frase

Uma monade é um tipo dotado de uma forma de encadear passos em que cada passo depende do resultado do anterior, e em que o «contexto» (falha, efeitos, não-determinismo) é propagado automaticamente. É tudo. Os famosos tipos Maybe, Either, IO e lista são todos monades porque cada um define este encadeamento para o seu próprio tipo de contexto.

As duas operações

A typeclass Monad é definida por duas funções:

return :: a -> m a          -- wrap a plain value into the monad
(>>=)  :: m a -> (a -> m b) -> m b   -- "bind": feed the result into the next step

>>= (que se pronuncia «bind») é o seu cerne: recebe um valor em contexto e uma função que produz o valor seguinte em contexto, e junta-os — gerindo o contexto por si.

do-notation: a mesma coisa, de forma legível

Código-fonte num ecrã de computador
Código-fonte no ecrã — a do-notation é açúcar sintático por cima do operador bind.

Os blocos do são apenas açúcar por cima de >>=. Estes são equivalentes:

-- with bind
getLine >>= \name -> putStrLn ("Hi " ++ name)

-- with do-notation
do name <- getLine
   putStrLn ("Hi " ++ name)

A do-notation permite-lhe escrever código sequenciado que transporta contexto e que se lê como passos imperativos, mantendo-se puramente funcional.

As monades que já utiliza

  • Maybe — o contexto é «pode estar ausente». O bind faz curto-circuito em Nothing, por isso encadeia pesquisas sem verificações de null aninhadas.
  • Either — o contexto é «pode falhar com um erro». Como Maybe, mas transporta a razão da falha.
  • IO — o contexto é «executa efeitos secundários». A monade IO é a forma como Haskell sequencia efeitos mantendo-se puro; main :: IO () é o ponto de entrada.
  • Lista ([]) — o contexto é «vários resultados». O bind explora cada combinação (não-determinismo).

Utiliza monades desde o seu primeiro main — IO é uma delas.

Por que as monades importam

Permitem-lhe abstrair a canalização do tratamento de falhas, dos efeitos e da sequenciação num padrão reutilizável, para que o mesmo código em estilo do funcione em contextos muito diferentes. É por isso que Haskell consegue manter IO separado e explícito mantendo-se ergonómico. Para executar tudo isto, precisa da toolchain — veja instalar Haskell com o GHCup e o guia do compilador GHC, e experimente os exemplos ao vivo no GHCi.

FAQ

O que é uma monade em termos simples? Um tipo que lhe permite encadear cálculos que transportam um contexto (falha, efeitos, vários resultados), propagando esse contexto automaticamente através do operador bind.

IO é uma monade? Sim — IO é a monade que Haskell usa para sequenciar efeitos secundários mantendo-se puro. O seu main é executado dentro dela.

É preciso compreender a teoria das categorias? Não. Pode usar as monades de forma produtiva em Haskell conhecendo apenas return, >>= e a do-notation; a teoria é opcional.

Qual é a diferença entre Maybe e Either? Ambas modelam uma possível falha; Maybe diz apenas «ausente» (Nothing), enquanto Either transporta um valor de erro que explica a falha.

Guia independente, mantido pela comunidade. coldwa.st é um site de recursos de programação; este artigo é um texto explicativo novo e original sobre as monades em Haskell, sem afiliação com qualquer projeto nem redigido por ele. O código reflete o comportamento padrão de Haskell — confirme com a documentação atual do GHC.