Vou usar uma descrição independente de idioma de mônadas como esta, primeiro descrevendo monóides:
Um monóide é (aproximadamente) um conjunto de funções que usam algum tipo como parâmetro e retornam o mesmo tipo.
Uma mônada é (aproximadamente) um conjunto de funções que tomam um tipo de invólucro como parâmetro e retorna o mesmo tipo de invólucro.
Observe que são descrições, não definições. Sinta-se livre para atacar essa descrição!
Portanto, em uma linguagem OO, uma mônada permite composições de operações como:
Flier<Duck> m = new Flier<Duck>(duck).takeOff().flyAround().land()
Observe que a mônada define e controla a semântica dessas operações, em vez da classe contida.
Tradicionalmente, em uma linguagem OO, usamos uma hierarquia e herança de classes para fornecer essas semânticas. Assim teríamos uma Bird
classe com métodos takeOff()
, flyAround()
e land()
, e Duck herdaria aqueles.
Mas então temos problemas com pássaros que penguin.takeOff()
não voam, porque falham. Temos que recorrer ao lançamento e manuseio de exceção.
Além disso, quando dizemos que o Penguin é a Bird
, encontramos problemas com herança múltipla, por exemplo, se também temos uma hierarquia de Swimmer
.
Essencialmente, estamos tentando colocar classes em categorias (com desculpas aos sujeitos da teoria da categoria) e definir semântica por categoria, em vez de em classes individuais. Mas as mônadas parecem um mecanismo muito mais claro para fazer isso do que as hierarquias.
Portanto, nesse caso, teríamos uma Flier<T>
mônada como o exemplo acima:
Flier<Duck> m = new Flier<Duck>(duck).takeOff().flyAround().land()
... e nunca instanciamos a Flier<Penguin>
. Poderíamos até usar a digitação estática para impedir que isso aconteça, talvez com uma interface de marcador. Ou verificação de capacidade de execução para salvar. Mas, na verdade, um programador nunca deve colocar um pinguim no Flier, no mesmo sentido em que nunca deve dividir por zero.
Além disso, é mais aplicável em geral. Um panfleto não precisa ser um pássaro. Por exemplo Flier<Pterodactyl>
, ou Flier<Squirrel>
, sem alterar a semântica desses tipos individuais.
Uma vez que classificamos a semântica por funções composíveis em um contêiner - em vez de com hierarquias de tipos - ele resolve os problemas antigos com classes que "meio que fazem, meio que não" se encaixam em uma hierarquia específica. Também permite fácil e claramente várias semânticas para uma classe, como Flier<Duck>
também Swimmer<Duck>
. Parece que estamos lutando com uma incompatibilidade de impedâncias ao classificar o comportamento com hierarquias de classes. Mônadas lidam com isso com elegância.
Portanto, minha pergunta é: da mesma maneira que passamos a favor da composição sobre a herança, também faz sentido favorecer as mônadas sobre a herança?
(BTW, eu não tinha certeza se isso deveria estar aqui ou no Comp Sci, mas isso parece mais um problema prático de modelagem. Mas talvez seja melhor por lá.)