Para respeitar os leitores rápidos, começo primeiro por uma definição precisa, continuo com uma explicação mais rápida e simples em inglês e depois passo para exemplos.
Aqui está uma definição concisa e precisa, ligeiramente reformulada:
Uma mônada (em ciência da computação) é formalmente um mapa que:
envia todo tipo X
de alguma linguagem de programação para um novo tipo T(X)
(chamado "tipo de T
-computações com valores em X
");
equipado com uma regra para compor duas funções da forma
f:X->T(Y)
e g:Y->T(Z)
para uma função g∘f:X->T(Z)
;
de uma maneira associativa no sentido evidente e unital em relação a uma determinada função unitária chamada pure_X:X->T(X)
, a ser considerada como tendo um valor para a computação pura que simplesmente retorna esse valor.
Portanto, em palavras simples, uma mônada é uma regra para passar de qualquer tipo X
para outro tipoT(X)
, e uma regra para passar de duas funções f:X->T(Y)
e g:Y->T(Z)
(que você gostaria de compor, mas não pode) para uma nova funçãoh:X->T(Z)
. Que, no entanto, não é a composição em sentido matemático estrito. Estamos basicamente "dobrando" a composição da função ou redefinindo como as funções são compostas.
Além disso, exigimos que a regra de composição da mônada satisfaça os axiomas matemáticos "óbvios":
- Associatividade : Compor
f
com g
e depois com h
(de fora) deve ser o mesmo que compor g
com h
e depois com f
(de dentro).
- Propriedade unital : a composição
f
da função de identidade de ambos os lados deve render f
.
Novamente, em palavras simples, não podemos simplesmente enlouquecer redefinindo nossa composição de funções como gostamos:
- Primeiro, precisamos da associatividade para poder compor várias funções em uma linha
f(g(h(k(x)))
, por exemplo , e não nos preocupar em especificar os pares de funções de composição da ordem. Como a regra da mônada apenas prescreve como compor um par de funções , sem esse axioma, precisaríamos saber qual par é composto primeiro e assim por diante. (Observe que é diferente da propriedade de comutatividade que f
compôs com g
era a mesma que g
compôs com f
, o que não é necessário).
- E segundo, precisamos da propriedade unital, que é simplesmente dizer que as identidades compõem trivialmente a maneira como as esperamos. Assim, podemos refatorar funções com segurança sempre que essas identidades puderem ser extraídas.
Então, novamente em resumo: uma mônada é a regra das funções de extensão e composição de tipos que satisfazem os dois axiomas - associatividade e propriedade unital.
Em termos práticos, você deseja que a mônada seja implementada para você pelo idioma, compilador ou estrutura que cuidaria das funções de composição para você. Portanto, você pode se concentrar em escrever a lógica de sua função, em vez de se preocupar em como a execução deles é implementada.
É basicamente isso, em poucas palavras.
Sendo matemático profissional, prefiro evitar chamar h
a "composição" de f
e g
. Porque matematicamente, não é. Chamá-lo de "composição" pressupõe incorretamente que h
é a verdadeira composição matemática, o que não é. Nem sequer é determinado exclusivamente por f
e g
. Em vez disso, é o resultado da nova "regra de composição" de nossa mônada. O que pode ser totalmente diferente da composição matemática real, mesmo que exista!
Para torná-lo menos seco, deixe-me tentar ilustrá-lo pelo exemplo que estou anotando em seções pequenas, para que você possa pular direto ao ponto.
Exceção lançada como exemplos da Mônada
Suponha que desejemos compor duas funções:
f: x -> 1 / x
g: y -> 2 * y
Mas f(0)
não está definido, portanto, uma exceção e
é lançada. Então, como você pode definir o valor composicional g(f(0))
? Lance uma exceção novamente, é claro! Talvez o mesmo e
. Talvez uma nova exceção atualizada e1
.
O que exatamente acontece aqui? Primeiro, precisamos de novos valores de exceção (diferentes ou iguais). Você pode chamá-los nothing
ou null
qualquer outra coisa, mas a essência permanece a mesma - eles devem ser novos valores, por exemplo, não deve ser um number
em nosso exemplo aqui. Prefiro não ligar para eles null
para evitar confusão com como null
pode ser implementado em qualquer idioma específico. Da mesma forma, prefiro evitar, nothing
porque muitas vezes está associado a null
, o que, em princípio, é o que null
deve fazer; no entanto, esse princípio geralmente é desviado por quaisquer razões práticas.
O que é exceção exatamente?
Este é um assunto trivial para qualquer programador experiente, mas gostaria de soltar algumas palavras apenas para extinguir qualquer worm de confusão:
Exceção é um objeto que encapsula informações sobre como ocorreu o resultado inválido da execução.
Isso pode variar desde jogar fora todos os detalhes e retornar um único valor global (como NaN
ou null
) ou gerar uma longa lista de logs ou o que exatamente aconteceu, enviá-lo para um banco de dados e replicar por toda a camada de armazenamento de dados distribuídos;)
A diferença importante entre esses dois exemplos extremos de exceção é que, no primeiro caso, não há efeitos colaterais . No segundo existem. O que nos leva à questão (de mil dólares):
As exceções são permitidas em funções puras?
Resposta mais curta : Sim, mas apenas quando eles não causam efeitos colaterais.
Resposta mais longa. Para ser pura, a saída da sua função deve ser determinada exclusivamente por sua entrada. Portanto, alteramos nossa função f
enviando 0
para o novo valor abstrato e
que chamamos de exceção. Garantimos que o valor e
não contenha informações externas que não sejam determinadas exclusivamente por nossa entrada, o que é x
. Então, aqui está um exemplo de exceção sem efeito colateral:
e = {
type: error,
message: 'I got error trying to divide 1 by 0'
}
E aqui está um com efeito colateral:
e = {
type: error,
message: 'Our committee to decide what is 1/0 is currently away'
}
Na verdade, só tem efeitos colaterais se essa mensagem puder mudar no futuro. Mas se é garantido que nunca mude, esse valor se tornará previsível de forma única e, portanto, não haverá efeito colateral.
Para torná-lo ainda mais idiota. Uma função que retorna 42
sempre é claramente pura. Mas se alguém louco decide criar 42
uma variável com esse valor alterado, a mesma função deixa de ser pura sob as novas condições.
Observe que estou usando a notação literal de objeto por simplicidade para demonstrar a essência. Infelizmente, as coisas estão bagunçadas em linguagens como JavaScript, onde error
não é um tipo que se comporta da maneira que queremos aqui em relação à composição de funções, enquanto tipos reais gostam null
ou NaN
não se comportam dessa maneira, mas passam por algumas artificiais e nem sempre intuitivas digitar conversões.
Extensão de tipo
Como queremos variar a mensagem dentro de nossa exceção, estamos realmente declarando um novo tipo E
para todo o objeto de exceção e, então, é isso que maybe number
acontece, além de seu nome confuso, que deve ser do tipo number
ou do novo tipo de exceção E
, então é realmente a união number | E
de number
e E
. Em particular, depende de como queremos construir E
, o que não é sugerido nem refletido no nome maybe number
.
O que é composição funcional?
É os matemáticos funções operação de tomada
f: X -> Y
e g: Y -> Z
e construção de sua composição como função h: X -> Z
satisfazendo h(x) = g(f(x))
. O problema com esta definição ocorre quando o resultado f(x)
não é permitido como argumento de g
.
Em matemática, essas funções não podem ser compostas sem trabalho extra. A solução estritamente matemática para o exemplo acima de f
e g
é remover 0
do conjunto de definições de f
. Com esse novo conjunto de definições (novo tipo mais restritivo de x
), f
torna-se passível de composição g
.
No entanto, não é muito prático em programação restringir o conjunto de definições desse f
tipo. Em vez disso, exceções podem ser usadas.
Ou como uma outra abordagem, os valores artificiais são criados como NaN
, undefined
, null
, Infinity
etc. Então você avaliar 1/0
a Infinity
e 1/-0
a -Infinity
. E, em seguida, force o novo valor de volta à sua expressão, em vez de lançar a exceção. Levando a resultados que você pode ou não achar previsível:
1/0 // => Infinity
parseInt(Infinity) // => NaN
NaN < 0 // => false
false + 1 // => 1
E estamos de volta aos números regulares prontos para seguir em frente;)
O JavaScript nos permite continuar executando expressões numéricas a qualquer custo, sem gerar erros, como no exemplo acima. Isso significa que também permite compor funções. Qual é exatamente o objetivo da mônada - é uma regra compor funções que satisfaçam os axiomas, conforme definido no início desta resposta.
Mas a regra da função de composição, decorrente da implementação do JavaScript para lidar com erros numéricos, é uma mônada?
Para responder a essa pergunta, tudo que você precisa é verificar os axiomas (deixados como exercício como parte da pergunta aqui;).
A exceção de lançamento pode ser usada para construir uma mônada?
De fato, uma mônada mais útil seria a regra que prescreve que, se f
lança exceção para alguns x
, o mesmo ocorre com sua composição g
. Além disso, torne a exceção E
globalmente única, com apenas um valor possível ( objeto terminal na teoria das categorias). Agora, os dois axiomas são verificáveis instantaneamente e temos uma mônada muito útil. E o resultado é o que é conhecido como a mônada talvez .