Em termos que um programador de OOP entenderia (sem nenhum fundo de programação funcional), o que é uma mônada?
Que problema ele resolve e quais são os locais mais comuns em que é usado?
EDITAR:
Para esclarecer o tipo de entendimento que eu estava procurando, digamos que você estava convertendo um aplicativo FP que tinha mônadas em um aplicativo OOP. O que você faria para portar as responsabilidades das mônadas para o aplicativo OOP?
Respostas:
ATUALIZAÇÃO: Esta questão foi objeto de uma série de blogs imensamente longa, que você pode ler em Monads - obrigado pela ótima pergunta!
Uma mônada é um "amplificador" de tipos que obedece a certas regras e que possui determinadas operações .
Primeiro, o que é um "amplificador de tipos"? Por isso, quero dizer algum sistema que permite que você escolha um tipo e o transforme em um tipo mais especial. Por exemplo, em C # considere
Nullable<T>
. Este é um amplificador de tipos. Ele permite que você escolha um tipo, digamosint
, e adicione um novo recurso a esse tipo, a saber, que agora ele pode ser nulo quando não podia antes.Como segundo exemplo, considere
IEnumerable<T>
. É um amplificador de tipos. Permite que você escolha um tipo, digamos,string
e adicione um novo recurso a esse tipo, ou seja, agora você pode criar uma sequência de cadeias de caracteres a partir de qualquer número de cadeias únicas.Quais são as "certas regras"? Resumidamente, existe uma maneira sensata de as funções do tipo subjacente trabalharem no tipo amplificado, de modo que sigam as regras normais da composição funcional. Por exemplo, se você tem uma função com números inteiros, diga
então a função correspondente em
Nullable<int>
poderá fazer com que todos os operadores e chamadas funcionem juntos "da mesma maneira" que antes.(Isso é incrivelmente vago e impreciso; você pediu uma explicação que não assumisse nada sobre o conhecimento da composição funcional.)
Quais são as "operações"?
Existe uma operação de "unidade" (confusa, às vezes chamada de operação "de retorno") que pega um valor de um tipo simples e cria o valor monádico equivalente. Isso, em essência, fornece uma maneira de pegar um valor de um tipo não amplificado e transformá-lo em um valor do tipo amplificado. Pode ser implementado como um construtor em uma linguagem OO.
Há uma operação "bind" que recebe um valor monádico e uma função que pode transformar o valor e retorna um novo valor monádico. Bind é a operação principal que define a semântica da mônada. Permite transformar operações no tipo não amplificado em operações no tipo amplificado, que obedecem às regras de composição funcional mencionadas anteriormente.
Geralmente, existe uma maneira de recuperar o tipo não amplificado do tipo amplificado. Estritamente falando, esta operação não é obrigada a ter uma mônada. (Embora seja necessário se você deseja uma comonada . Não consideraremos isso mais adiante neste artigo.)
Mais uma vez, tome
Nullable<T>
como exemplo. Você pode transformar umint
em umNullable<int>
com o construtor. O compilador C # cuida da maioria dos "anulamentos" anuláveis para você, mas, se não o fez, a transformação do levantamento é direta: uma operação, por exemplo,é transformado em
E transformar as
Nullable<int>
costas em umaint
é feito com oValue
propriedadeÉ a transformação da função que é o bit principal. Observe como a semântica real da operação anulável - que uma operação em um
null
propaga onull
- é capturada na transformação. Podemos generalizar isso.Suponha que você tenha uma função de
int
paraint
, como nosso originalM
. Você pode facilmente transformar isso em uma função que recebeint
e retorna aNullable<int>
porque você pode simplesmente executar o resultado através do construtor anulável. Agora, suponha que você tenha este método de ordem superior:Veja o que você pode fazer com isso? Qualquer método que recebe
int
e retorna umint
, ou recebe umint
e retorna aNullable<int>
agora pode ter a semântica anulável aplicada a ele .Além disso: suponha que você tenha dois métodos
e você quer compor eles:
Ou seja,
Z
é a composição deX
eY
. Mas você não pode fazer isso porqueX
pega umint
eY
retorna umNullable<int>
. Mas como você tem a operação "bind", pode fazer este trabalho:A operação de ligação em uma mônada é o que faz a composição de funções em tipos amplificados funcionar. As "regras" acima mencionadas são de que a mônada preserva as regras da composição normal das funções; que a composição com funções de identidade resulta na função original, que a composição é associativa e assim por diante.
Em C #, "Vincular" é chamado "SelectMany". Veja como ele funciona na mônada de sequência. Precisamos ter duas coisas: transformar um valor em uma sequência e vincular operações em seqüências. Como bônus, também temos "transformar uma sequência novamente em um valor". Essas operações são:
A regra da mônada anulável era "combinar duas funções que produzem nulidades juntas, verifique se a interna resulta em nulo; se isso ocorrer, produza nulo, se não resultar, então chame a externa com o resultado". Essa é a semântica desejada de anulável.
A regra da mônada de sequência é "combinar duas funções que produzem seqüências juntas, aplicar a função externa a todos os elementos produzidos pela função interna e concatenar todas as seqüências resultantes juntas". A semântica fundamental das mônadas é capturada nos métodos
Bind
/SelectMany
; este é o método que diz o que a mônada realmente significa .Nós podemos fazer ainda melhor. Suponha que você tenha uma sequência de ints e um método que receba ints e resulte em sequências de strings. Poderíamos generalizar a operação de ligação para permitir a composição de funções que recebem e retornam tipos amplificados diferentes, desde que as entradas de uma correspondam às saídas da outra:
Então agora podemos dizer "amplifique esse monte de números inteiros individuais em uma sequência de números inteiros. Transforme esse número inteiro específico em um monte de cadeias, amplificado em uma sequência de cadeias. Agora monte as duas operações: amplifique esse grupo de números inteiros na concatenação de números inteiros. todas as sequências de cordas ". Mônadas permitem compor suas amplificações.
É como perguntar "que problemas o padrão singleton resolve?", Mas vou tentar.
Mônadas são normalmente usadas para resolver problemas como:
C # usa mônadas em seu design. Como já mencionado, o padrão anulável é altamente semelhante ao "talvez mônada". O LINQ é inteiramente construído a partir de mônadas; o
SelectMany
método é o que faz o trabalho semântico da composição das operações. (Erik Meijer gosta de salientar que todas as funções do LINQ poderiam realmente ser implementadas porSelectMany
; todo o resto é apenas uma conveniência.)A maioria dos idiomas OOP não possui um sistema de tipos suficientemente rico para representar o próprio padrão de mônada diretamente; você precisa de um sistema de tipos que suporte tipos mais altos que tipos genéricos. Então, eu não tentaria fazer isso. Em vez disso, eu implementaria tipos genéricos que representam cada mônada e implementaria métodos que representam as três operações necessárias: transformar um valor em um valor amplificado, (talvez) transformar um valor amplificado em um valor e transformar uma função em valores não amplificados em uma função em valores amplificados.
Um bom lugar para começar é como implementamos o LINQ em C #. Estude o
SelectMany
método; é a chave para entender como a mônada de sequência funciona em c #. É um método muito simples, mas muito poderoso!Sugestão, leitura adicional:
fonte
Por que precisamos de mônadas?
Então, nós temos um primeiro grande problema. Este é um programa:
f(x) = 2 * x
g(x,y) = x / y
Como podemos dizer o que deve ser executado primeiro ? Como podemos formar uma sequência ordenada de funções (isto é, um programa ) usando não mais que funções?
Solução: compor funções . Se você quiser primeiro
g
e depoisf
, basta escreverf(g(x,y))
. OK mas ...Mais problemas: algumas funções podem falhar (ou seja
g(2,0)
, dividir por 0). Não temos "exceções" no FP . Como resolvemos isso?Solução: vamos permitir que as funções retornem dois tipos de coisas : em vez de ter
g : Real,Real -> Real
(função de dois reais em um real), vamos permitirg : Real,Real -> Real | Nothing
(função de dois reais em (real ou nada)).Mas as funções devem (para ser mais simples) retornar apenas uma coisa .
Solução: vamos criar um novo tipo de dados a serem retornados, um " tipo de boxe " que encerra talvez um real ou seja simplesmente nada. Por isso, podemos ter
g : Real,Real -> Maybe Real
. OK mas ...O que acontece agora
f(g(x,y))
?f
não está pronto para consumir umMaybe Real
. E não queremos alterar todas as funções com as quais podemos nos conectarg
para consumir umMaybe Real
.Solução: vamos ter uma função especial para "conectar" / "compor" / "vincular" funções . Dessa forma, podemos, nos bastidores, adaptar a saída de uma função para alimentar a seguinte.
No nosso caso:
g >>= f
(conectar / comporg
paraf
). Queremos>>=
obterg
a produção, inspecioná-la e, no caso deNothing
simplesmente não ligarf
e retornarNothing
; ou, pelo contrário, extraia a caixaReal
e alimentef
com ela. (Este algoritmo é apenas a implementação de>>=
para oMaybe
tipo).Surgem muitos outros problemas que podem ser resolvidos usando esse mesmo padrão: 1. Use uma "caixa" para codificar / armazenar diferentes significados / valores e tenha funções como
g
essa que retornam esses "valores em caixa". 2. Tenha compositores / vinculadoresg >>= f
para ajudar a conectarg
a saída àf
entrada, para que não tenhamos que mudarf
.Problemas notáveis que podem ser resolvidos usando esta técnica são:
tendo um estado global que todas as funções na sequência de funções ("o programa") podem compartilhar: solução
StateMonad
.Não gostamos de "funções impuras": funções que produzem resultados diferentes para a mesma entrada. Portanto, vamos marcar essas funções, fazendo com que elas retornem um valor marcado / em caixa:
IO
mônada.Felicidade total !!!!
fonte
State
eIO
, com nenhum deles, bem como com o significado exato de "mônada"Eu diria que a analogia OO mais próxima das mônadas é o " padrão de comando ".
No padrão de comando, você quebra uma declaração ou expressão comum em um objeto de comando . O objeto de comando expõe um método de execução que executa a instrução agrupada. Assim, a instrução é transformada em objetos de primeira classe que podem ser passados e executados à vontade. Os comandos podem ser compostos para que você possa criar um objeto de programa encadeando e aninhando objetos de comando.
Os comandos são executados por um objeto separado, o invocador . O benefício de usar o padrão de comando (em vez de apenas executar uma série de instruções comuns) é que diferentes invocadores podem aplicar lógica diferente à maneira como os comandos devem ser executados.
O padrão de comando pode ser usado para adicionar (ou remover) recursos de idioma que não são suportados pelo idioma do host. Por exemplo, em uma linguagem OO hipotética sem exceções, você pode adicionar semânticas de exceção expondo os métodos "try" e "throw" aos comandos. Quando um comando chama throw, o invocador retorna pela lista (ou árvore) de comandos até a última chamada "try". Por outro lado, você pode remover a semântica de exceção de um idioma (se você acredita que as exceções são ruins ) capturando todas as exceções lançadas por cada comando individual e transformando-as em códigos de erro que são passados para o próximo comando.
Uma semântica de execução ainda mais sofisticada, como transações, execução não determinística ou continuações, pode ser implementada dessa maneira em uma linguagem que não a suporta nativamente. É um padrão bastante poderoso se você pensar sobre isso.
Agora, na realidade, os padrões de comando não são usados como um recurso geral da linguagem como este. A sobrecarga de transformar cada instrução em uma classe separada levaria a uma quantidade insuportável de código clichê. Mas, em princípio, pode ser usado para resolver os mesmos problemas que as mônadas são usadas para resolver em fp.
fonte
Em termos de programação OO, uma mônada é uma interface (ou mais provavelmente uma mixin), parametrizada por um tipo, com dois métodos,
return
ebind
que descrevem:O problema que ele resolve é o mesmo tipo de problema que você esperaria de qualquer interface, a saber: "Eu tenho várias classes diferentes que fazem coisas diferentes, mas parecem fazer essas coisas diferentes de uma maneira que tem uma semelhança subjacente. posso descrever essa semelhança entre eles, mesmo que as próprias classes não sejam realmente subtipos de algo mais próximo do que a própria classe 'Object'? "
Mais especificamente, a
Monad
"interface" é semelhanteIEnumerator
ouIIterator
requer um tipo que por si só. O principal "ponto" doMonad
pensamento é poder conectar operações com base no tipo de interior, até ao ponto de ter um novo "tipo interno", mantendo - ou até aprimorando - a estrutura de informações da classe principal.fonte
return
na verdade, não seria um método na mônada, porque não leva uma instância de mônada como argumento. (ie: não existe isso / auto) #Você tem uma apresentação recente " Monadologie - ajuda profissional sobre ansiedade de tipo ", da Christopher League (12 de julho de 2010), que é bastante interessante sobre tópicos de continuação e mônada.
O vídeo que acompanha esta apresentação (slideshare) está realmente disponível no vimeo .
A parte Monad começa em torno de 37 minutos neste vídeo de uma hora e começa com o slide 42 da sua apresentação de 58 slides.
É apresentado como "o principal padrão de design para programação funcional", mas a linguagem usada nos exemplos é Scala, que é OOP e funcional.
Você pode ler mais sobre o Monad em Scala na postagem do blog " Monads - Outra maneira de abstrair os cálculos em Scala ", de Debasish Ghosh (27 de março de 2008).
Então, por exemplo (em Scala):
Option
é uma mônadaList
é MônadaA Mônada é muito importante em Scala por causa da sintaxe conveniente criada para aproveitar as estruturas da Mônada:
for
compreensão em Scala :é traduzido pelo compilador para:
A abstração da chave é a
flatMap
, que liga a computação através do encadeamento.Cada chamada de
flatMap
retorna o mesmo tipo de estrutura de dados (mas de valor diferente), que serve como entrada para o próximo comando da cadeia.No snippet acima, flatMap recebe como entrada um fechamento
(SomeType) => List[AnotherType]
e retorna aList[AnotherType]
. O ponto importante a ser observado é que todos os flatMaps usam o mesmo tipo de fechamento que a entrada e retornam o mesmo tipo que a saída.É isso que "liga" o encadeamento da computação - todo item da sequência na compreensão precisa respeitar esta mesma restrição de tipo.
Se você realizar duas operações (que podem falhar) e passar o resultado para a terceira, como:
mas sem tirar vantagem do Monad, você obtém código OOP complicado, como:
enquanto que com o Monad, você pode trabalhar com os tipos reais (
Venue
,User
) como todas as operações e manter oculto o material de verificação da opção, tudo por causa dos mapas planos da sintaxe for:A parte de rendimento só será executada se todas as três funções tiverem
Some[X]
; qualquer umNone
seria retornado diretamente paraconfirm
.Assim:
A propósito, o Monad não é apenas o modelo de computação usado no FP:
fonte
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:
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çõesf:X->T(Y)
eg: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":
f
comg
e depois comh
(de fora) deve ser o mesmo que comporg
comh
e depois comf
(de dentro).f
da função de identidade de ambos os lados deve renderf
.Novamente, em palavras simples, não podemos simplesmente enlouquecer redefinindo nossa composição de funções como gostamos:
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 quef
compôs comg
era a mesma queg
compôs comf
, o que não é necessário).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" def
eg
. Porque matematicamente, não é. Chamá-lo de "composição" pressupõe incorretamente queh
é a verdadeira composição matemática, o que não é. Nem sequer é determinado exclusivamente porf
eg
. 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:
Mas
f(0)
não está definido, portanto, uma exceçãoe
é lançada. Então, como você pode definir o valor composicionalg(f(0))
? Lance uma exceção novamente, é claro! Talvez o mesmoe
. Talvez uma nova exceção atualizadae1
.O que exatamente acontece aqui? Primeiro, precisamos de novos valores de exceção (diferentes ou iguais). Você pode chamá-los
nothing
ounull
qualquer outra coisa, mas a essência permanece a mesma - eles devem ser novos valores, por exemplo, não deve ser umnumber
em nosso exemplo aqui. Prefiro não ligar para elesnull
para evitar confusão com comonull
pode ser implementado em qualquer idioma específico. Da mesma forma, prefiro evitar,nothing
porque muitas vezes está associado anull
, o que, em princípio, é o quenull
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
ounull
) 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
enviando0
para o novo valor abstratoe
que chamamos de exceção. Garantimos que o valore
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 aqui está um com efeito colateral:
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 criar42
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 gostamnull
ouNaN
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 quemaybe number
acontece, além de seu nome confuso, que deve ser do tiponumber
ou do novo tipo de exceçãoE
, então é realmente a uniãonumber | E
denumber
eE
. Em particular, depende de como queremos construirE
, o que não é sugerido nem refletido no nomemaybe number
.O que é composição funcional?
É os matemáticos funções operação de tomada
f: X -> Y
eg: Y -> Z
e construção de sua composição como funçãoh: X -> Z
satisfazendoh(x) = g(f(x))
. O problema com esta definição ocorre quando o resultadof(x)
não é permitido como argumento deg
.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
eg
é remover0
do conjunto de definições def
. Com esse novo conjunto de definições (novo tipo mais restritivo dex
),f
torna-se passível de composiçãog
.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ê avaliar1/0
aInfinity
e1/-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: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 algunsx
, o mesmo ocorre com sua composiçãog
. Além disso, torne a exceçãoE
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 .fonte
Uma mônada é um tipo de dados que encapsula um valor e ao qual, essencialmente, duas operações podem ser aplicadas:
return x
cria um valor do tipo de mônada que encapsulax
m >>= f
(leia-o como "o operador de ligação") aplica a funçãof
ao valor na mônadam
É isso que uma mônada é. Existem mais alguns detalhes técnicos , mas basicamente essas duas operações definem uma mônada. A verdadeira questão é: "O que uma mônada faz ?", E isso depende das listas de mônadas, mônadas, Maybes são mônadas, operações de IO são mônadas. Tudo o que isso significa quando dizemos que essas coisas são mônadas é que elas têm a interface de mônada de
return
e>>=
.fonte
bind
função que deve ser definida para cada tipo monádico, não é? Essa seria uma boa razão para não confundir ligação com composição, pois existe uma definição única para composição, embora não possa haver apenas uma definição única para uma função de ligação, existe uma por tipo monádico, se bem entendi.Da wikipedia :
Eu acredito que isso explica muito bem.
fonte
Vou tentar fazer a definição mais curta que posso gerenciar usando termos de POO:
Uma classe genérica
CMonadic<T>
é uma mônada se definir pelo menos os seguintes métodos:e se as leis a seguir se aplicarem a todos os tipos T e seus possíveis valores t
identidade esquerda:
identidade certa
associatividade:
Exemplos :
Uma mônada da lista pode ter:
E o flatMap na lista [1,2,3] poderia funcionar assim:
Iteráveis e Observáveis também podem ser monádicos, assim como Promessas e Tarefas.
Comentário :
Mônadas não são tão complicadas. A
flatMap
função é muito parecida com a mais encontradamap
. Ele recebe um argumento de função (também conhecido como delegado), que pode chamar (imediatamente ou mais tarde, zero ou mais vezes) com um valor proveniente da classe genérica. Ele espera que a função passada também envolva seu valor de retorno no mesmo tipo de classe genérica. Para ajudar nisso, ele fornececreate
um construtor que pode criar uma instância dessa classe genérica a partir de um valor. O resultado do retorno do flatMap também é uma classe genérica do mesmo tipo, geralmente compactando os mesmos valores que estavam contidos nos resultados do retorno de uma ou mais aplicações do flatMap nos valores anteriormente contidos. Isso permite que você conecte o flatMap quantas vezes quiser:Acontece que esse tipo de classe genérica é útil como modelo básico para um grande número de coisas. Este (juntamente com os jargonismos da teoria das categorias) é a razão pela qual as mônadas parecem tão difíceis de entender ou explicar. Eles são uma coisa muito abstrata e só se tornam obviamente úteis quando são especializados.
Por exemplo, você pode modelar exceções usando contêineres monádicos. Cada contêiner conterá o resultado da operação ou o erro que ocorreu. A próxima função (delegar) na cadeia de retornos de chamada flatMap somente será chamada se a anterior empacotou um valor no contêiner. Caso contrário, se um erro foi compactado, o erro continuará a se propagar pelos contêineres encadeados até que seja encontrado um contêiner que tenha uma função de manipulador de erros anexada por meio de um método chamado
.orElse()
(esse método seria uma extensão permitida)Notas : Os idiomas funcionais permitem escrever funções que podem operar em qualquer tipo de classe genérica monádica. Para que isso funcione, seria necessário escrever uma interface genérica para as mônadas. Eu não sei se é possível escrever essa interface em c #, mas tanto quanto eu sei que não é:
fonte
Se uma mônada tem uma interpretação "natural" no OO depende da mônada. Em uma linguagem como Java, você pode traduzir a mônada talvez para a linguagem de verificação de ponteiros nulos, para que os cálculos que falham (ou seja, produzam Nada em Haskell) emitam ponteiros nulos como resultado. Você pode converter a mônada do estado no idioma gerado, criando uma variável mutável e métodos para alterar seu estado.
Uma mônada é um monóide na categoria de endofunitores.
As informações que a frase reúne são muito profundas. E você trabalha em uma mônada com qualquer linguagem imperativa. Uma mônada é um idioma específico do domínio "sequenciado". Satisfaz certas propriedades interessantes que, juntas, fazem de uma mônada um modelo matemático de "programação imperativa". Haskell facilita a definição de linguagens imperativas pequenas (ou grandes), que podem ser combinadas de várias maneiras.
Como programador de OO, você usa a hierarquia de classes da sua linguagem para organizar os tipos de funções ou procedimentos que podem ser chamados em um contexto, o que você chama de objeto. Uma mônada também é uma abstração dessa idéia, na medida em que mônadas diferentes podem ser combinadas de maneiras arbitrárias, "importando" efetivamente todos os métodos da submonada para o escopo.
Arquitetonicamente, usa-se assinaturas de tipo para expressar explicitamente quais contextos podem ser usados para calcular um valor.
Pode-se usar transformadores de mônada para esse fim, e há uma coleção de alta qualidade de todas as mônadas "padrão":
com transformadores de mônada correspondentes e classes de tipo. As classes de tipo permitem uma abordagem complementar para combinar mônadas unificando suas interfaces, para que mônadas concretas possam implementar uma interface padrão para o "tipo" de mônada. Por exemplo, o módulo Control.Monad.State contém uma classe MonadState sm e (State s) é uma instância do formulário
A longa história é que uma mônada é um functor que anexa "contexto" a um valor, que tem uma maneira de injetar um valor na mônada e que tem uma maneira de avaliar valores em relação ao contexto anexado a ela, pelo menos de maneira restrita.
Assim:
é uma função que injeta um valor do tipo a em uma "ação" de mônada do tipo m a.
é uma função que executa uma ação de mônada, avalia seu resultado e aplica uma função ao resultado. O interessante de (>> =) é que o resultado está na mesma mônada. Em outras palavras, em m >> = f, (>> =) retira o resultado de m e o vincula a f, de modo que o resultado esteja na mônada. (Como alternativa, podemos dizer que (>> =) puxa f para m e aplica-o ao resultado.) Como conseqüência, se tivermos f :: a -> mb eg g :: b -> mc, podemos ações de "sequência":
Ou, usando "notação"
O tipo para (>>) pode estar iluminado. Isto é
Corresponde ao operador (;) em linguagens procedurais como C. Permite notação como:
Na lógica matemática e filosófica, temos quadros e modelos, que são "naturalmente" modelados com monadismo. Uma interpretação é uma função que examina o domínio do modelo e calcula o valor de verdade (ou generalizações) de uma proposição (ou fórmula, sob generalizações). Numa lógica modal da necessidade, poderíamos dizer que uma proposição é necessária se for verdadeira em "todo mundo possível" - se for verdadeira em relação a todos os domínios admissíveis. Isso significa que um modelo em uma linguagem para uma proposição pode ser reificado como um modelo cujo domínio consiste na coleção de modelos distintos (um correspondente a cada mundo possível). Toda mônada possui um método chamado "join", que nivela as camadas, o que implica que toda ação de mônada cujo resultado é uma ação de mônada pode ser incorporada na mônada.
Mais importante, isso significa que a mônada é fechada na operação "empilhamento de camadas". É assim que os transformadores de mônada funcionam: eles combinam mônadas, fornecendo métodos "semelhantes a junções" para tipos como
para que possamos transformar uma ação em (MaybeT m) em uma ação em m, efetivamente recolhendo camadas. Nesse caso, runMaybeT :: MaybeT ma -> m (Maybe a) é o nosso método de junção. (MaybeT m) é uma mônada, e MaybeT :: m (Talvez a) -> MaybeT ma é efetivamente um construtor para um novo tipo de ação de mônada em m.
Uma mônada livre para um functor é a mônada gerada pelo empilhamento f, com a implicação de que toda sequência de construtores para f é um elemento da mônada livre (ou, mais exatamente, algo com a mesma forma que a árvore de sequências de construtores para f) Mônadas livres são uma técnica útil para a construção de mônadas flexíveis com uma quantidade mínima de placa de caldeira. Em um programa Haskell, eu poderia usar mônadas gratuitas para definir mônadas simples para "programação de sistema de alto nível" para ajudar a manter a segurança de tipos (estou apenas usando tipos e suas declarações. As implementações são diretas com o uso de combinadores):
O monadismo é a arquitetura subjacente para o que você pode chamar de padrão "intérprete" ou "comando", abstraído para sua forma mais clara, pois toda computação monádica deve ser "executada", pelo menos trivialmente. (O sistema de tempo de execução executa a mônada de IO para nós e é o ponto de entrada para qualquer programa Haskell. A IO "conduz" o restante dos cálculos, executando as ações de IO em ordem).
O tipo de junção também é onde obtemos a afirmação de que uma mônada é um monóide na categoria de endofunitores. A junção é tipicamente mais importante para fins teóricos, em virtude de seu tipo. Mas entender o tipo significa entender mônadas. Os tipos de união do transformador de junção e do tipo monad são efetivamente composições de endofunitores, no sentido da composição da função. Para colocá-lo em uma pseudo-linguagem semelhante a Haskell,
Foo :: m (ma) <-> (m. M) a
fonte
Uma mônada é uma matriz de funções
(Pst: uma matriz de funções é apenas uma computação).
Na verdade, em vez de uma matriz verdadeira (uma função em uma matriz celular), você tem essas funções encadeadas por outra função >> =. O >> = permite adaptar os resultados da função i para alimentar a função i + 1, realizar cálculos entre eles ou, mesmo, não chamar a função i + 1.
Os tipos usados aqui são "tipos com contexto". Ou seja, um valor com uma "tag". As funções encadeadas devem ter um "valor simples" e retornar um resultado marcado. Um dos deveres de >> = é extrair um valor nu do seu contexto. Há também a função "return", que pega um valor nu e o coloca com uma tag.
Um exemplo com Talvez . Vamos usá-lo para armazenar um número inteiro simples no qual fazer cálculos.
Apenas para mostrar que as mônadas são uma matriz de funções com operações auxiliares, considere o equivalente ao exemplo acima, apenas usando uma matriz real de funções
E seria usado assim:
fonte
>>=
é um operador\x -> x >>= k >>= l >>= m
é uma matriz de funções, o mesmo ocorreh . g . f
, o que não envolve mônadas.Em termos OO, uma mônada é um contêiner fluente.
O requisito mínimo é uma definição
class <A> Something
que suporta um construtorSomething(A a)
e pelo menos um métodoSomething<B> flatMap(Function<A, Something<B>>)
Indiscutivelmente, também conta se a sua classe monad possui algum método com assinatura
Something<B> work()
que preservam as regras da classe - o compilador utiliza o flatMap no momento da compilação.Por que uma mônada é útil? Porque é um contêiner que permite operações com capacidade de cadeia que preservam a semântica. Por exemplo,
Optional<?>
preserva a semântica da IsPresent paraOptional<String>
,Optional<Integer>
,Optional<MyClass>
, etc.Como um exemplo aproximado,
Note que começamos com uma string e terminamos com um número inteiro. Muito legal.
No OO, pode demorar um pouco para acenar com a mão, mas qualquer método em Algo que retorne outra subclasse de Algo atende ao critério de uma função de contêiner que retorna um contêiner do tipo original.
É assim que você preserva a semântica - ou seja, o significado e as operações do contêiner não mudam, apenas envolvem e aprimoram o objeto dentro do contêiner.
fonte
Mônadas em uso típico são o equivalente funcional dos mecanismos de manipulação de exceção da programação procedural.
Nas linguagens processuais modernas, você coloca um manipulador de exceção em torno de uma sequência de instruções, qualquer uma das quais pode gerar uma exceção. Se alguma das instruções lançar uma exceção, a execução normal da sequência de instruções será interrompida e transferida para um manipulador de exceções.
As linguagens de programação funcional, no entanto, evitam filosoficamente os recursos de manipulação de exceções devido à natureza "goto" deles. A perspectiva da programação funcional é que as funções não devem ter "efeitos colaterais", como exceções que interrompem o fluxo do programa.
Na realidade, efeitos colaterais não podem ser descartados no mundo real devido principalmente à E / S. Mônadas na programação funcional são usadas para lidar com isso, fazendo um conjunto de chamadas de funções em cadeia (qualquer uma das quais pode produzir um resultado inesperado) e transformando qualquer resultado inesperado em dados encapsulados que ainda podem fluir com segurança pelas chamadas de função restantes.
O fluxo de controle é preservado, mas o evento inesperado é encapsulado e tratado com segurança.
fonte
Uma simples explicação de Mônadas com um estudo de caso da Marvel está aqui .
Mônadas são abstrações usadas para sequenciar funções dependentes que são eficazes. Eficaz aqui significa que eles retornam um tipo no formato F [A], por exemplo, Opção [A] onde Opção é F, chamada construtor de tipo. Vamos ver isso em 2 etapas simples
No entanto, se a função retornar um tipo de efeito como Opção [A], ou seja, A => F [B], a composição não funcionará para ir para B, precisamos de A => B, mas temos A => F [B].
Precisamos de um operador especial, "bind", que saiba fundir essas funções que retornam F [A].
A função "bind" é definida para o F específico .
Também existe "retorno" , do tipo A => F [A] para qualquer A , definido também para esse F específico . Para ser uma Mônada, F deve ter essas duas funções definidas para ela.
Assim, podemos construir uma função eficaz A => F [B] a partir de qualquer função pura A => B ,
mas um determinado F também pode definir suas próprias funções especiais "internas" opacas de tais tipos que um usuário não pode definir a si mesmo (em uma linguagem pura ), como
fonte
Estou compartilhando minha compreensão das mônadas, o que pode não ser teoricamente perfeito. Mônadas são sobre propagação de contexto . Mônada é: você define algum contexto para alguns dados (ou tipos de dados) e, em seguida, define como esse contexto será transportado com os dados por todo o pipeline de processamento. E definir propagação de contexto é principalmente definir como mesclar vários contextos (do mesmo tipo). Usar Mônadas também significa garantir que esses contextos não sejam acidentalmente removidos dos dados. Por outro lado, outros dados sem contexto podem ser trazidos para um contexto novo ou existente. Então, esse conceito simples pode ser usado para garantir a correção do tempo de compilação de um programa.
fonte
Se você já usou o Powershell, os padrões que Eric descreveu devem parecer familiares. Os cmdlets do PowerShell são mônadas; composição funcional é representada por um pipeline .
A entrevista de Jeffrey Snover com Erik Meijer entra em mais detalhes.
fonte
Veja minha resposta para "O que é uma mônada?"
Começa com um exemplo motivador, funciona através do exemplo, deriva um exemplo de mônada e define formalmente "mônada".
Ele não assume conhecimento de programação funcional e usa pseudocódigo com
function(argument) := expression
sintaxe com as expressões mais simples possíveis.Este programa C ++ é uma implementação da mônada de pseudocódigo. (Para referência:
M
é o construtor de tipos,feed
é a operação "bind" ewrap
é a operação "return").fonte
De um ponto de vista prático (resumindo o que foi dito em muitas respostas anteriores e artigos relacionados), parece-me que um dos "propósitos" (ou utilidades) fundamentais da mônada é alavancar as dependências implícitas nas invocações de métodos recursivos aka composição da função (ou seja, quando f1 chama f2 chama f3, f3 precisa ser avaliado antes de f2 antes de f1) para representar a composição sequencial de uma maneira natural, especialmente no contexto de um modelo de avaliação lento (ou seja, composição sequencial como uma sequência simples) , por exemplo, "f3 (); f2 (); f1 ();" em C - o truque é especialmente óbvio se você pensar em um caso em que f3, f2 e f1 realmente não retornam nada [seu encadeamento como f1 (f2 (f3)) é artificial, puramente destinado a criar sequência]).
Isso é especialmente relevante quando os efeitos colaterais estão envolvidos, ou seja, quando algum estado é alterado (se f1, f2, f3 não tivesse efeitos colaterais, não importaria em que ordem eles são avaliados; o que é uma grande propriedade de puro linguagens funcionais, para poder paralelizar esses cálculos, por exemplo). Quanto mais funções puras, melhor.
Penso que, desse ponto de vista restrito, as mônadas poderiam ser vistas como açúcar sintático para idiomas que favorecem a avaliação preguiçosa (que avaliam as coisas somente quando absolutamente necessário, seguindo uma ordem que não depende da apresentação do código) e que não têm outros meios de representar a composição sequencial. O resultado final é que seções do código que são "impuras" (ou seja, que têm efeitos colaterais) podem ser apresentadas naturalmente, de maneira imperativa, mas são claramente separadas das funções puras (sem efeitos colaterais), que podem ser avaliado preguiçosamente.
Este é apenas um aspecto, como advertido aqui .
fonte
A explicação mais simples que consigo pensar é que as mônadas são uma maneira de compor funções com resultados embelezados (também conhecida como composição de Kleisli). Uma função "embelished" tem a assinatura
a -> (b, smth)
ondea
eb
são tipos (penseInt
,Bool
) que podem ser diferentes uns dos outros, mas não necessariamente - esmth
é o "contexto" ou o "enfeite".Este tipo de funções também pode ser escrito
a -> m b
ondem
é equivalente ao "embelezamento"smth
. Portanto, são funções que retornam valores no contexto (pense em funções que registram suas ações, ondesmth
está a mensagem de log; ou funções que executam input \ output e seus resultados dependem do resultado da ação de E / S).Uma mônada é uma interface ("typeclass") que faz com que o implementador diga como compor essas funções. O implementador precisa definir uma função de composição
(a -> m b) -> (b -> m c) -> (a -> m c)
para qualquer tipom
que queira implementar a interface (essa é a composição de Kleisli).Portanto, se dissermos que temos um tipo de tupla
(Int, String)
representando resultados de cálculos emInt
s que também registram suas ações,(_, String)
sendo o "embelezamento" - o log da ação - e duas funçõesincrement :: Int -> (Int, String)
etwoTimes :: Int -> (Int, String)
queremos obter uma funçãoincrementThenDouble :: Int -> (Int, String)
que é a composição das duas funções que também levam em consideração os logs.No exemplo dado, uma implementação em mônada das duas funções se aplica ao valor inteiro 2
incrementThenDouble 2
(que é igual atwoTimes (increment 2)
) retornaria(6, " Adding 1. Doubling 3.")
para resultados intermediáriosincrement 2
iguais(3, " Adding 1.")
etwoTimes 3
iguais a(6, " Doubling 3.")
A partir dessa função de composição Kleisli, pode-se derivar as funções monádicas usuais.
fonte