Tendo examinado brevemente Haskell recentemente, qual seria uma explicação breve, sucinta e prática sobre o que é essencialmente uma mônada?
Descobri que a maioria das explicações que encontrei é bastante inacessível e carece de detalhes práticos.
Tendo examinado brevemente Haskell recentemente, qual seria uma explicação breve, sucinta e prática sobre o que é essencialmente uma mônada?
Descobri que a maioria das explicações que encontrei é bastante inacessível e carece de detalhes práticos.
Respostas:
Primeiro: o termo mônada é um pouco vazio se você não é um matemático. Um termo alternativo é construtor de computação, que é um pouco mais descritivo do que eles realmente são úteis.
Você pede exemplos práticos:
Exemplo 1: Compreensão da lista :
[x*2 | x<-[1..10], odd x]
Essa expressão retorna as duplas de todos os números ímpares no intervalo de 1 a 10. Muito útil!
Acontece que este é realmente apenas um açúcar sintático para algumas operações dentro da mônada da Lista. A mesma compreensão da lista pode ser escrita como:
do
x <- [1..10]
guard (odd x)
return (x * 2)
Ou até:
[1..10] >>= (\x -> guard (odd x) >> return (x*2))
Exemplo 2: Entrada / Saída :
do
putStrLn "What is your name?"
name <- getLine
putStrLn ("Welcome, " ++ name ++ "!")
Ambos os exemplos usam mônadas, construtores de computação AKA. O tema comum é que a mônada encadeia operações de alguma maneira específica e útil. Na compreensão da lista, as operações são encadeadas de modo que, se uma operação retorna uma lista, as seguintes operações são executadas em todos os itens da lista. A mônada de E / S, por outro lado, executa as operações sequencialmente, mas transmite uma "variável oculta", que representa "o estado do mundo", o que nos permite escrever o código de E / S de uma maneira pura e funcional.
Acontece que o padrão de operações de encadeamento é bastante útil e é usado para muitas coisas diferentes em Haskell.
Outro exemplo são as exceções: usando a Error
mônada, as operações são encadeadas de forma que sejam executadas seqüencialmente, exceto se um erro for gerado; nesse caso, o restante da cadeia será abandonado.
Tanto a sintaxe de compreensão da lista quanto a doação são açúcar sintático para operações de encadeamento usando o >>=
operador. Uma mônada é basicamente apenas um tipo que suporta o >>=
operador.
Exemplo 3: Um analisador
Este é um analisador muito simples que analisa uma string ou um número entre aspas:
parseExpr = parseString <|> parseNumber
parseString = do
char '"'
x <- many (noneOf "\"")
char '"'
return (StringValue x)
parseNumber = do
num <- many1 digit
return (NumberValue (read num))
As operações char
, digit
etc. são bastante simples. Eles combinam ou não. A mágica é a mônada que gerencia o fluxo de controle: as operações são executadas seqüencialmente até que uma correspondência falhe; nesse caso, a mônada retorna para a última <|>
e tenta a próxima opção. Novamente, uma maneira de encadear operações com alguma semântica adicional e útil.
Exemplo 4: Programação assíncrona
Os exemplos acima estão em Haskell, mas o F # também suporta mônadas. Este exemplo é roubado de Don Syme :
let AsyncHttp(url:string) =
async { let req = WebRequest.Create(url)
let! rsp = req.GetResponseAsync()
use stream = rsp.GetResponseStream()
use reader = new System.IO.StreamReader(stream)
return reader.ReadToEnd() }
Este método busca uma página da web. A linha de perfuração é o uso de GetResponseAsync
- na verdade aguarda a resposta em um thread separado, enquanto o thread principal retorna da função. As últimas três linhas são executadas no encadeamento gerado quando a resposta é recebida.
Na maioria dos outros idiomas, você teria que criar explicitamente uma função separada para as linhas que lidam com a resposta. A async
mônada é capaz de "dividir" o bloco por conta própria e adiar a execução da segunda metade. (A async {}
sintaxe indica que o fluxo de controle no bloco é definido pela async
mônada.)
Como eles trabalham
Então, como uma mônada pode fazer tudo isso? O que realmente acontece em um bloco de tarefas (ou em uma expressão de cálculo, como são chamados em F #), é que todas as operações (basicamente todas as linhas) são agrupadas em uma função anônima separada. Essas funções são então combinadas usando o bind
operador (escrito >>=
em Haskell). Como a bind
operação combina funções, ela pode executá-las como achar necessário: sequencialmente, várias vezes, ao contrário, descarta algumas, executa algumas em um encadeamento separado quando lhe apetecer e assim por diante.
Como exemplo, esta é a versão expandida do código IO do exemplo 2:
putStrLn "What is your name?"
>>= (\_ -> getLine)
>>= (\name -> putStrLn ("Welcome, " ++ name ++ "!"))
Isso é mais feio, mas também é mais óbvio o que realmente está acontecendo. O >>=
operador é o ingrediente mágico: ele pega um valor (no lado esquerdo) e o combina com uma função (no lado direito), para produzir um novo valor. Esse novo valor é então utilizado pelo próximo >>=
operador e novamente combinado com uma função para produzir um novo valor. >>=
pode ser visto como um mini-avaliador.
Observe que >>=
está sobrecarregado para tipos diferentes, portanto, cada mônada tem sua própria implementação >>=
. (Todas as operações na cadeia precisam ser do tipo da mesma mônada, caso contrário, o >>=
operador não funcionará.)
A implementação mais simples possível >>=
apenas pega o valor à esquerda e aplica-o à função à direita e retorna o resultado, mas, como dito anteriormente, o que torna todo o padrão útil é quando há algo extra acontecendo na implementação da mônada de >>=
.
Há alguma inteligência adicional em como os valores são passados de uma operação para a seguinte, mas isso requer uma explicação mais profunda do sistema do tipo Haskell.
Resumindo
Em termos de Haskell, uma mônada é um tipo parametrizado que é uma instância da classe de tipo Mônada, que define >>=
junto com alguns outros operadores. Em termos leigos, uma mônada é apenas um tipo para o qual a >>=
operação está definida.
Por si >>=
só, é apenas uma maneira complicada de encadear funções, mas com a presença da notação que oculta o "encanamento", as operações monádicas acabam sendo uma abstração muito agradável e útil, útil em muitos lugares da linguagem e útil para criar seus próprios mini-idiomas no idioma.
Por que as mônadas são difíceis?
Para muitos aprendizes de Haskell, as mônadas são um obstáculo que atingem como uma parede de tijolos. Não é que as mônadas sejam complexas, mas a implementação depende de muitos outros recursos avançados do Haskell, como tipos parametrizados, classes de tipos e assim por diante. O problema é que o Haskell I / O é baseado em mônadas, e é provavelmente uma das primeiras coisas que você deseja entender ao aprender um novo idioma - afinal, não é muito divertido criar programas que não produzem nenhum resultado. Não tenho solução imediata para esse problema de galinha e ovo, exceto tratar a E / S como "mágica acontece aqui" até que você tenha experiência suficiente com outras partes da linguagem. Desculpa.
Excelente blog sobre mônadas: http://adit.io/posts/2013-04-17-functors,_applicatives,_and_monads_in_pictures.html
Explicar "o que é uma mônada" é como dizer "o que é um número?" Usamos números o tempo todo. Mas imagine que você conheceu alguém que não sabia nada sobre números. Como diabos você explicaria o que são números? E como você começaria a descrever por que isso pode ser útil?
O que é uma mônada? A resposta curta: é uma maneira específica de encadear operações.
Em essência, você está escrevendo etapas de execução e vinculando-as à "função de ligação". (Em Haskell, ele é chamado >>=
.) Você pode gravar as chamadas para o operador de ligação, ou pode usar a sintaxe sugar, que faz com que o compilador insira essas chamadas de função para você. Mas, de qualquer maneira, cada etapa é separada por uma chamada para essa função de ligação.
Portanto, a função de ligação é como um ponto e vírgula; separa as etapas em um processo. O trabalho da função de ligação é pegar a saída da etapa anterior e alimentá-la na próxima etapa.
Isso não parece muito difícil, certo? Mas há mais de um tipo de mônada. Por quê? Quão?
Bem, a função de ligação pode apenas pegar o resultado de uma etapa e alimentá-lo para a próxima etapa. Mas se isso é "tudo" a mônada faz ... isso realmente não é muito útil. E isso é importante para entender: toda mônada útil faz outra coisa além de ser apenas uma mônada. Toda mônada útil tem um "poder especial", o que a torna única.
(Uma mônada que não faz nada de especial é chamada de "mônada de identidade". Mais ou menos como a função de identidade, isso soa como uma coisa totalmente sem sentido, mas acaba não sendo ... Mas isso é outra história ™.)
Basicamente, cada mônada tem sua própria implementação da função de ligação. E você pode escrever uma função de ligação, de modo que faça coisas interessantes entre as etapas de execução. Por exemplo:
Se cada etapa retornar um indicador de êxito / falha, você poderá executar a ligação na próxima etapa apenas se a anterior tiver sido bem-sucedida. Dessa forma, uma etapa com falha aborta toda a sequência "automaticamente", sem nenhum teste condicional de você. (A Mônada de Falha .)
Estendendo essa idéia, você pode implementar "exceções". (A Mônada de erro ou Mônaco de exceção ). Como você está definindo elas mesmas, e não como um recurso de idioma, é possível definir como elas funcionam. (Por exemplo, talvez você queira ignorar as duas primeiras exceções e abortar apenas quando uma terceira exceção for lançada.)
Você pode fazer com que cada etapa retorne vários resultados e faça com que a função de vinculação faça um loop sobre eles, alimentando cada uma delas na próxima etapa. Dessa forma, você não precisa continuar escrevendo loops em todo o lugar ao lidar com vários resultados. A função de ligação "automaticamente" faz tudo isso para você. (A lista Mônada .)
Além de passar um "resultado" de uma etapa para outra, você pode fazer com que a função de ligação passe dados extras também. Esses dados agora não aparecem no seu código-fonte, mas você ainda pode acessá-los de qualquer lugar, sem precisar passá-los manualmente para todas as funções. (O leitor Mônada .)
Você pode fazer isso para que os "dados extras" possam ser substituídos. Isso permite simular atualizações destrutivas , sem realmente fazer atualizações destrutivas. (A Mônada do Estado e seu primo, a Mônada do Escritor .)
Como você está apenas simulando atualizações destrutivas, pode fazer coisas triviais que seriam impossíveis com atualizações destrutivas reais . Por exemplo, você pode desfazer a última atualização ou reverter para uma versão mais antiga .
Você pode fazer uma mônada onde os cálculos podem ser pausados , para que você possa pausar seu programa, entrar e mexer nos dados internos do estado e depois retomar.
Você pode implementar "continuações" como uma mônada. Isso permite que você quebre a mente das pessoas!
Tudo isso e muito mais é possível com mônadas. Obviamente, tudo isso também é perfeitamente possível sem mônadas. É drasticamente mais fácil usar mônadas.
Na verdade, ao contrário do entendimento comum das mônadas, elas nada têm a ver com o estado. Mônadas são simplesmente uma maneira de embrulhar as coisas e fornecer métodos para executar operações no material embrulhado sem desembrulhá-lo.
Por exemplo, você pode criar um tipo para quebrar outro, em Haskell:
data Wrapped a = Wrap a
Para embrulhar coisas que definimos
return :: a -> Wrapped a
return x = Wrap x
Para executar operações sem desembrulhar, digamos que você tenha uma função f :: a -> b
, você pode fazer isso para elevar essa função para agir com base em valores agrupados:
fmap :: (a -> b) -> (Wrapped a -> Wrapped b)
fmap f (Wrap x) = Wrap (f x)
Isso é tudo o que há para entender. No entanto, verifica-se que há uma função mais geral para fazer esse levantamento , que é bind
:
bind :: (a -> Wrapped b) -> (Wrapped a -> Wrapped b)
bind f (Wrap x) = f x
bind
pode fazer um pouco mais do que fmap
, mas não vice-versa. Na verdade, só fmap
pode ser definido em termos de bind
e return
. Portanto, ao definir uma mônada, você indica o tipo (aqui estava Wrapped a
) e depois diz como as operações return
e bind
as funções funcionam.
O legal é que isso acaba sendo um padrão tão geral que aparece em todo o lugar, encapsular o estado de maneira pura é apenas um deles.
Para um bom artigo sobre como as mônadas podem ser usadas para introduzir dependências funcionais e, assim, controlar a ordem de avaliação, como é usada na mônada de IO da Haskell, consulte IO Inside .
Quanto à compreensão das mônadas, não se preocupe muito com isso. Leia sobre eles o que achar interessante e não se preocupe se não entender imediatamente. Depois, basta mergulhar em um idioma como Haskell. As mônadas são uma dessas coisas em que a compreensão entra em seu cérebro pela prática; um dia você percebe de repente que as entende.
Mas, você poderia ter inventado as mônadas!
sigfpe diz:
Mas tudo isso introduz mônadas como algo esotérico que precisa de explicação. Mas o que quero argumentar é que eles não são esotéricos. De fato, diante de vários problemas na programação funcional, você seria levado, inexoravelmente, a certas soluções, exemplos de mônadas. Na verdade, espero que você os invente agora, se ainda não o fez. É então um pequeno passo para perceber que todas essas soluções são de fato a mesma solução disfarçada. E depois de ler isso, você estará em melhor posição para entender outros documentos nas mônadas, porque reconhecerá tudo o que vê como algo que já inventou.
Muitos dos problemas que as mônadas tentam resolver estão relacionados à questão dos efeitos colaterais. Então, vamos começar com eles. (Observe que as mônadas permitem fazer mais do que lidar com efeitos colaterais, em particular muitos tipos de objetos de contêiner podem ser vistos como mônadas. Algumas das introduções a mônadas acham difícil conciliar esses dois usos diferentes das mônadas e concentrar-se em apenas um ou o outro.)
Em uma linguagem de programação imperativa como o C ++, funções não se comportam como as funções da matemática. Por exemplo, suponha que tenhamos uma função C ++ que pega um único argumento de ponto flutuante e retorna um resultado de ponto flutuante. Superficialmente, pode parecer um pouco uma função matemática que mapeia reais para reais, mas uma função C ++ pode fazer mais do que apenas retornar um número que depende de seus argumentos. Ele pode ler e gravar os valores de variáveis globais, além de gravar a saída na tela e receber informações do usuário. Em uma linguagem funcional pura, no entanto, uma função só pode ler o que é fornecido em seus argumentos e a única maneira de afetar o mundo é através dos valores que ela retorna.
Uma mônada é um tipo de dados que possui duas operações: >>=
(aka bind
) e return
(aka unit
). return
pega um valor arbitrário e cria uma instância da mônada com ele. >>=
pega uma instância da mônada e mapeia uma função sobre ela. (Você já pode ver que uma mônada é um tipo estranho de tipo de dados, pois na maioria das linguagens de programação não era possível escrever uma função que pega um valor arbitrário e cria um tipo a partir dela. As mônadas usam um tipo de polimorfismo paramétrico .)
Na notação Haskell, a interface monad é escrita
class Monad m where
return :: a -> m a
(>>=) :: forall a b . m a -> (a -> m b) -> m b
Essas operações devem obedecer a certas "leis", mas isso não é muito importante: as "leis" apenas codificam a maneira como as implementações sensíveis das operações devem se comportar (basicamente, isso >>=
e return
devem concordar sobre como os valores são transformados em instâncias mônadas e isso >>=
é associativo).
As mônadas não são apenas sobre estado e E / S: elas abstraem um padrão comum de computação que inclui trabalhar com estado, E / S, exceções e não-determinismo. Provavelmente, as mônadas mais simples de entender são listas e tipos de opções:
instance Monad [ ] where
[] >>= k = []
(x:xs) >>= k = k x ++ (xs >>= k)
return x = [x]
instance Monad Maybe where
Just x >>= k = k x
Nothing >>= k = Nothing
return x = Just x
onde []
e :
são os construtores da lista, ++
é o operador de concatenação e Just
e Nothing
são os Maybe
construtores. Ambas as mônadas encapsulam padrões comuns e úteis de computação em seus respectivos tipos de dados (observe que nenhum deles tem nada a ver com efeitos colaterais ou E / S).
Você realmente precisa escrever um código Haskell não trivial para apreciar o que são as mônadas e por que são úteis.
Você deve primeiro entender o que é um functor. Antes disso, entenda as funções de ordem superior.
Uma função de ordem superior é simplesmente uma função que aceita uma função como argumento.
Um functor é qualquer construção de tipo T
para a qual exista uma função de ordem superior, chame-a map
, que transforma uma função do tipo a -> b
(dados dois tipos a
e b
) em uma função T a -> T b
. Essa map
função também deve obedecer às leis de identidade e composição, de modo que as seguintes expressões retornem verdadeiras para todos p
e q
(notação de Haskell):
map id = id
map (p . q) = map p . map q
Por exemplo, um construtor de tipo chamado List
é um functor se for equipado com uma função do tipo (a -> b) -> List a -> List b
que obedece às leis acima. A única implementação prática é óbvia. A List a -> List b
função resultante itera sobre a lista fornecida, chamando a (a -> b)
função para cada elemento e retorna a lista dos resultados.
A mônada é essencialmente apenas um functor T
com dois métodos extra, join
, do tipo T (T a) -> T a
, e unit
(às vezes chamado return
, fork
ou pure
) do tipo a -> T a
. Para listas em Haskell:
join :: [[a]] -> [a]
pure :: a -> [a]
Por que isso é útil? Porque você pode, por exemplo, map
sobre uma lista com uma função que retorna uma lista. Join
pega a lista resultante de listas e as concatena. List
é uma mônada porque isso é possível.
Você pode escrever uma função que sim map
, então join
. Essa função é chamada bind
, ou flatMap
, ou (>>=)
, ou (=<<)
. Normalmente, é assim que uma instância de mônada é fornecida no Haskell.
Uma mônada deve satisfazer certas leis, a saber, que join
devem ser associativas. Isso significa que, se você tiver um valor x
do tipo [[[a]]]
, join (join x)
deverá ser igual join (map join x)
. E pure
deve ser uma identidade para join
tal join (pure x) == x
.
[Isenção de responsabilidade: ainda estou tentando gritar completamente as mônadas. A seguir, é exatamente o que eu entendi até agora. Se estiver errado, espero que alguém com conhecimento me ligue no tapete.]
Arnar escreveu:
Mônadas são simplesmente uma maneira de embrulhar as coisas e fornecer métodos para executar operações no material embrulhado sem desembrulhá-lo.
É exatamente isso. A ideia é a seguinte:
Você pega algum tipo de valor e o envolve com algumas informações adicionais. Assim como o valor é de um determinado tipo (por exemplo, um número inteiro ou uma string), as informações adicionais são de um determinado tipo.
Por exemplo, essa informação extra pode ser um Maybe
ou um IO
.
Então você tem alguns operadores que permitem operar os dados agrupados enquanto carregam essas informações adicionais. Esses operadores usam as informações adicionais para decidir como alterar o comportamento da operação no valor agrupado.
Por exemplo, a Maybe Int
pode ser um Just Int
ou Nothing
. Agora, se você adicionar um Maybe Int
a a Maybe Int
, o operador verificará se ambos estão Just Int
dentro de si e, se houver, desembrulhará os Int
s, passará a eles o operador de adição e reorganizará o resultado Int
em um novo Just Int
(que é válido Maybe Int
) e, portanto, retorne a Maybe Int
. Mas se um deles fosse Nothing
interno, esse operador retornaria imediatamente Nothing
, o que novamente é válido Maybe Int
. Dessa forma, você pode fingir que seus Maybe Int
números são apenas normais e executar matemática regularmente com eles. Se você conseguir um Nothing
, suas equações ainda produzirão o resultado certo - sem que você precise fazer verificações de lixo em Nothing
todos os lugares .
Mas o exemplo é exatamente o que acontece Maybe
. Se a informação extra fosse um IO
, então esse operador especial definido para IO
s seria chamado e poderia fazer algo totalmente diferente antes de realizar a adição. (OK, somar dois IO Int
s provavelmente não faz sentido - ainda não tenho certeza.) (Além disso, se você prestou atenção ao Maybe
exemplo, notou que "agrupar um valor com coisas extras" nem sempre é correto. Mas é difícil para ser exato, correto e preciso sem ser inescrutável.)
Basicamente, "mônada" significa aproximadamente "padrão" . Mas, em vez de um livro cheio de padrões explicados informalmente e nomeados especificamente, agora você tem uma construção de linguagem - sintaxe e tudo - que permite declarar novos padrões como itens de seu programa . (A imprecisão aqui é que todos os padrões precisam seguir uma forma específica, de modo que uma mônada não é tão genérica quanto um padrão. Mas acho que esse é o termo mais próximo que a maioria das pessoas conhece e entende.)
E é por isso que as pessoas acham as mônadas tão confusas: porque são um conceito tão genérico. Perguntar o que faz de algo uma mônada é igualmente vago, como perguntar o que faz de algo um padrão.
Mas pense nas implicações de ter suporte sintático na linguagem para a idéia de um padrão: em vez de ter que ler o livro da Gangue dos Quatro e memorizar a construção de um padrão específico, basta escrever um código que implemente esse padrão de forma agnóstica, maneira genérica uma vez e então está pronto! Você pode reutilizar esse padrão, como Visitor ou Strategy ou Façade ou qualquer outra coisa, apenas decorando as operações em seu código com ele, sem ter que reimplementá-lo repetidamente!
É por isso que as pessoas que entendem as mônadas as acham tão úteis : não é um conceito de torre de marfim que os esnobes intelectuais se orgulham de entender (OK, isso também é claro, eles), mas na verdade torna o código mais simples.
M (M a) -> M a
. O fato de você poder transformar isso em um tipo M a -> (a -> M b) -> M b
é o que os torna úteis.
Depois de muito esforço, acho que finalmente entendi a mônada. Depois de reler minha longa crítica à resposta predominantemente votada, apresentarei esta explicação.
Há três perguntas que precisam ser respondidas para entender as mônadas:
Como observei em meus comentários originais, muitas explicações sobre mônadas são abordadas na questão número 3, sem e antes de realmente cobrir adequadamente a questão 2 ou a questão 1.
Por que você precisa de uma mônada?
Linguagens funcionais puras como Haskell são diferentes das linguagens imperativas como C ou Java, pois um programa funcional puro não é necessariamente executado em uma ordem específica, uma etapa de cada vez. Um programa Haskell é mais parecido com uma função matemática, na qual você pode resolver a "equação" em qualquer número de possíveis ordens. Isso confere uma série de benefícios, entre os quais se elimina a possibilidade de certos tipos de bugs, particularmente aqueles relacionados a coisas como "estado".
No entanto, existem certos problemas que não são tão simples de resolver com esse estilo de programação. Algumas coisas, como a programação do console e a E / S de arquivo, precisam que as coisas aconteçam em uma ordem específica ou que mantenham o estado. Uma maneira de lidar com esse problema é criar um tipo de objeto que represente o estado de uma computação e uma série de funções que tomam um objeto de estado como entrada e retornam um novo objeto de estado modificado.
Então, vamos criar um valor hipotético de "estado", que representa o estado de uma tela do console. exatamente como esse valor é construído não é importante, mas digamos que seja uma matriz de caracteres ascii de comprimento de byte que represente o que está atualmente visível na tela e uma matriz que represente a última linha de entrada inserida pelo usuário no pseudocódigo. Definimos algumas funções que assumem o estado do console, modificam-no e retornam um novo estado do console.
consolestate MyConsole = new consolestate;
Então, para fazer a programação do console, mas de uma maneira funcional pura, você precisaria aninhar muitas chamadas de função dentro de cada um.
consolestate FinalConsole = print(input(print(myconsole, "Hello, what's your name?")),"hello, %inputbuffer%!");
Programar dessa maneira mantém o estilo funcional "puro", enquanto força as alterações no console para acontecer em uma ordem específica. Mas, provavelmente, queremos fazer mais do que apenas algumas operações por vez, como no exemplo acima. As funções de aninhamento dessa maneira começarão a se tornar desajeitadas. O que queremos é um código que faça essencialmente a mesma coisa que acima, mas que seja escrito um pouco mais como este:
consolestate FinalConsole = myconsole:
print("Hello, what's your name?"):
input():
print("hello, %inputbuffer%!");
Esta seria realmente uma maneira mais conveniente de escrevê-lo. Como fazemos isso?
O que é uma mônada?
Depois de ter um tipo (como consolestate
) que você define, juntamente com várias funções projetadas especificamente para operar nesse tipo, você pode transformar todo o pacote dessas coisas em uma "mônada", definindo um operador como :
(bind) que automaticamente feeds retornam valores à esquerda, para parâmetros de função à direita, e um lift
operador que transforma funções normais, em funções que funcionam com esse tipo específico de operador de ligação.
Como uma mônada é implementada?
Veja outras respostas, que parecem bastante livres para entrar nos detalhes disso.
Depois de responder a esta pergunta há alguns anos, acredito que posso melhorar e simplificar essa resposta com ...
Uma mônada é uma técnica de composição de funções que externaliza o tratamento para alguns cenários de entrada usando uma função de composição bind
, para pré-processar a entrada durante a composição.
Na composição normal, a função, compose (>>)
é usada para aplicar a função composta ao resultado de seu predecessor em sequência. É importante ressaltar que a função que está sendo composta é necessária para lidar com todos os cenários de sua entrada.
(x -> y) >> (y -> z)
Esse design pode ser aprimorado reestruturando a entrada para que os estados relevantes sejam mais facilmente interrogados. Portanto, em vez de simplesmente y
o valor pode se tornar Mb
, por exemplo, (is_OK, b)
se y
incluir uma noção de validade.
Por exemplo, quando a entrada é possivelmente apenas um número, em vez de retornar uma sequência que pode conter um número obedientemente ou não, você pode reestruturar o tipo em um bool
indicando a presença de um número válido e um número na tupla, como bool * float
,. As funções compostas não precisariam mais analisar uma sequência de entrada para determinar se existe um número, mas poderiam apenas inspecionar a bool
parte de uma tupla.
(Ma -> Mb) >> (Mb -> Mc)
Aqui, novamente, a composição ocorre naturalmente compose
e, portanto, cada função deve lidar com todos os cenários de sua entrada individualmente, embora isso agora seja muito mais fácil.
No entanto, e se pudéssemos externalizar o esforço de interrogatório nos momentos em que lidar com um cenário é rotineiro? Por exemplo, e se o nosso programa não fizer nada quando a entrada não estiver boa como em quando is_OK
está false
. Se isso fosse feito, as funções compostas não precisariam lidar com esse cenário, simplificando dramaticamente seu código e efetuando outro nível de reutilização.
Para alcançar essa externalização, poderíamos usar uma função bind (>>=)
,, para executar o composition
invés de compose
. Como tal, em vez de simplesmente transferir valores da saída de uma função para a entrada de outra Bind
, inspecionaria a M
parte Ma
e decidiria se e como aplicar a função composta à a
. Obviamente, a função bind
seria definida especificamente para o nosso particular, de M
modo a poder inspecionar sua estrutura e executar o tipo de aplicação que desejamos. No entanto, o a
pode ser qualquer coisa, pois bind
apenas passa o não a
inspecionado para a função composta quando determina a aplicação necessária. Além disso, as próprias funções compostas não precisam mais lidar com oM
parte da estrutura de entrada, simplificando-os. Conseqüentemente...
(a -> Mb) >>= (b -> Mc)
ou mais sucintamente Mb >>= (b -> Mc)
Em resumo, uma mônada externaliza e, portanto, fornece comportamento padrão em relação ao tratamento de certos cenários de entrada, uma vez que a entrada é projetada para expô-los suficientemente. Esse design é um shell and content
modelo em que o shell contém dados relevantes para a aplicação da função composta e é interrogado e permanece disponível apenas para a bind
função.
Portanto, uma mônada é três coisas:
M
concha para armazenar informações relevantes da mônada, bind
função implementada para fazer uso dessas informações do shell em sua aplicação das funções compostas aos valores de conteúdo encontrados dentro do shell, e a -> Mb
produzindo resultados que incluem dados de gerenciamento monádicos.De um modo geral, a entrada para uma função é muito mais restritiva do que sua saída, o que pode incluir coisas como condições de erro; portanto, a Mb
estrutura de resultados é geralmente muito útil. Por exemplo, o operador de divisão não retorna um número quando o divisor é 0
.
Além disso, monad
s podem incluir funções de quebra automática que envolvem valores, a
no tipo monádico Ma
, e funções gerais a -> b
, em funções monádicas a -> Mb
, envolvendo os resultados após a aplicação. Obviamente bind
, tais funções de wrap são específicas para M
. Um exemplo:
let return a = [a]
let lift f a = return (f a)
O design da bind
função pressupõe estruturas de dados imutáveis e funções puras, outras coisas ficam complexas e garantias não podem ser feitas. Como tal, existem leis monádicas:
Dado...
M_
return = (a -> Ma)
f = (a -> Mb)
g = (b -> Mc)
Então...
Left Identity : (return a) >>= f === f a
Right Identity : Ma >>= return === Ma
Associative : Ma >>= (f >>= g) === Ma >>= ((fun x -> f x) >>= g)
Associativity
significa que bind
preserva a ordem da avaliação, independentemente de quando bind
é aplicada. Ou seja, na definição de Associativity
acima, a força de avaliação precoce do parêntesis binding
de f
e g
só vai resultar em uma função que espera Ma
, a fim de completar a bind
. Portanto, a avaliação de Ma
deve ser determinada antes que seu valor possa ser aplicado f
e esse resultado, por sua vez, aplicado g
.
Uma mônada é, efetivamente, uma forma de "operador de tipo". Isso fará três coisas. Primeiro, ele "quebra" (ou converte) um valor de um tipo em outro (normalmente chamado de "tipo monádico"). Em segundo lugar, disponibilizará todas as operações (ou funções) no tipo subjacente no tipo monádico. Finalmente, ele fornecerá suporte para combinar a si próprio com outra mônada para produzir uma mônada composta.
A "talvez mônada" é essencialmente o equivalente a "tipos anuláveis" no Visual Basic / C #. Ele pega um tipo não anulável "T" e o converte em "Nullable <T>" e depois define o que todos os operadores binários significam em um Nullable <T>.
Os efeitos colaterais são representados de forma semelhante. É criada uma estrutura que contém descrições dos efeitos colaterais ao lado do valor de retorno de uma função. As operações "levantadas" copiam os efeitos colaterais à medida que os valores são passados entre as funções.
Eles são chamados de "mônadas" em vez do nome mais fácil de entender de "operadores de tipo" por vários motivos:
(Veja também as respostas em O que é uma mônada? )
Uma boa motivação para as Mônadas é o Você poderia ter inventado as Mônadas de sigfpe (Dan Piponi) ! (E talvez você já tenha) . Existem muitos outros tutoriais de mônada , muitos dos quais tentam explicar erroneamente mônadas em "termos simples" usando várias analogias: esta é a falácia do tutorial de mônada ; Evite-os.
Como o DR MacIver diz em Diga-nos por que seu idioma é péssimo :
Então, coisas que eu odeio em Haskell:
Vamos começar com o óbvio. Tutoriais da Mônada. Não, não mônadas. Especificamente os tutoriais. Eles são infinitos, exagerados e querido deus, eles são tediosos. Além disso, nunca vi nenhuma evidência convincente de que eles realmente ajudem. Leia a definição da classe, escreva algum código, supere o nome assustador.
Você diz que entende a mônada Talvez? Bom, você está a caminho. Comece a usar outras mônadas e, mais cedo ou mais tarde, você entenderá o que são mônadas em geral.
[Se você é orientado matematicamente, pode querer ignorar as dezenas de tutoriais e aprender a definição, ou seguir as aulas de teoria das categorias :) A parte principal da definição é que um Monad M envolve um "construtor de tipo" que define para cada tipo existente "T", um novo tipo "MT" e algumas maneiras de alternar entre os tipos "regulares" e "M".]
Além disso, surpreendentemente, uma das melhores introduções às mônadas é na verdade um dos primeiros trabalhos acadêmicos que apresentam mônadas, as mônadas de Philip Wadler para programação funcional . Na verdade, ele tem exemplos motivadores práticos e não triviais , ao contrário de muitos dos tutoriais artificiais existentes.
Mônadas devem controlar o fluxo que tipos de dados abstratos são para dados.
Em outras palavras, muitos desenvolvedores estão confortáveis com a idéia de Conjuntos, Listas, Dicionários (ou Hashes ou Mapas) e Árvores. Dentro desses tipos de dados, há muitos casos especiais (por exemplo, InsertionOrderPreservingIdentityHashMap).
No entanto, quando confrontados com o "fluxo" do programa, muitos desenvolvedores não foram expostos a muito mais construções do que se, switch / case, faz, while, goto (grr) e (talvez) fechamentos.
Assim, uma mônada é simplesmente uma construção de fluxo de controle. Uma frase melhor para substituir a mônada seria 'tipo de controle'.
Como tal, uma mônada possui slots para lógica de controle, instruções ou funções - o equivalente em estruturas de dados seria dizer que algumas estruturas de dados permitem adicionar dados e removê-los.
Por exemplo, a mônada "if":
if( clause ) then block
na sua forma mais simples, tem dois slots - uma cláusula e um bloco. A if
mônada geralmente é criada para avaliar o resultado da cláusula e, se não for falsa, avaliar o bloco. Muitos desenvolvedores não são apresentados às mônadas quando aprendem 'se', e simplesmente não é necessário entender as mônadas para escrever uma lógica eficaz.
As mônadas podem se tornar mais complicadas, da mesma maneira que as estruturas de dados podem se tornar mais complicadas, mas existem muitas categorias amplas de mônadas que podem ter semântica semelhante, mas implementações e sintaxe diferentes.
Obviamente, da mesma maneira que as estruturas de dados podem ser iteradas sobre, ou atravessadas, mônadas podem ser avaliadas.
Os compiladores podem ou não ter suporte para mônadas definidas pelo usuário. Haskell certamente faz. Ioke possui alguns recursos semelhantes, embora o termo mônada não seja usado no idioma.
Meu tutorial favorito do Monad:
http://www.haskell.org/haskellwiki/All_About_Monads
(de 170.000 resultados em uma pesquisa no Google por "mônada tutorial"!)
@Stu: O objetivo das mônadas é permitir que você adicione (geralmente) a semântica seqüencial ao código puro; você pode até compor mônadas (usando Monad Transformers) e obter semânticas combinadas mais interessantes e complicadas, como analisar com tratamento de erros, estado compartilhado e registro, por exemplo. Tudo isso é possível em código puro, as mônadas apenas permitem abstraí-lo e reutilizá-lo em bibliotecas modulares (sempre boas em programação), além de fornecer sintaxe conveniente para torná-lo imperativo.
O Haskell já possui sobrecarga de operador [1]: ele usa classes de tipos da mesma forma que as interfaces em Java ou C #, mas o Haskell também permite também tokens não alfanuméricos como + && e> como identificadores de infix. É apenas uma sobrecarga de operador na sua maneira de ver se você quer dizer "sobrecarregar o ponto e vírgula" [2]. Parece magia negra e pedir problemas para "sobrecarregar o ponto e vírgula" (imagine os hackers Perl se divertindo com essa idéia), mas o ponto é que sem mônadas não há ponto e vírgula, pois o código puramente funcional não exige ou permite o seqüenciamento explícito.
Tudo isso parece muito mais complicado do que precisa. O artigo de sigfpe é bem legal, mas usa Haskell para explicá-lo, o que meio que falha em quebrar o problema do ovo e da galinha de entender Haskell para agredir mônadas e entender Mônadas para agredir Haskell.
[1] Este é um problema separado das mônadas, mas as mônadas usam o recurso de sobrecarga do operador da Haskell.
[2] Isso também é uma simplificação excessiva, pois o operador para encadear ações monádicas é >> = (pronunciado "bind"), mas há açúcar sintático ("do") que permite usar chaves e ponto e vírgula e / ou recuo e novas linhas.
Ultimamente, tenho pensado em mônadas de uma maneira diferente. Eu tenho pensado neles como abstraindo a ordem de execução de uma maneira matemática, o que possibilita novos tipos de polimorfismo.
Se você estiver usando uma linguagem imperativa e escrever algumas expressões em ordem, o código SEMPRE será executado exatamente nessa ordem.
E, no caso simples, quando você usa uma mônada, a mesma sensação é: você define uma lista de expressões que ocorrem em ordem. Exceto pelo fato de que, dependendo da mônada que você usa, seu código pode ser executado em ordem (como na mônada de IO), em paralelo sobre vários itens de uma vez (como na mônada de lista), ele pode parar no meio (como na mônada Maybe) , ele pode pausar parcialmente para ser retomado mais tarde (como em uma mônada de Retoma), pode retroceder e começar do começo (como em uma mônada de Transação) ou pode retroceder parcialmente para tentar outras opções (como em uma mônada da lógica) .
E como as mônadas são polimórficas, é possível executar o mesmo código em mônadas diferentes, dependendo de suas necessidades.
Além disso, em alguns casos, é possível combinar mônadas (com transformadores de mônada) para obter vários recursos ao mesmo tempo.
Ainda sou novo em mônadas, mas pensei em compartilhar um link que achei muito bom de ler (COM FOTOS !!): http://www.matusiak.eu/numerodix/blog/2012/3/11/ mônadas-para-leigo / (sem afiliação)
Basicamente, o conceito acolhedor e difuso que obtive do artigo foi o de que as mônadas são basicamente adaptadores que permitem que funções diferentes funcionem de maneira composível, ou seja, seja capaz de agrupar várias funções e misturá-las e combiná-las sem se preocupar com retorno inconsistente tipos e tal. Portanto, a função BIND é responsável por manter maçãs com maçãs e laranjas com laranjas quando estamos tentando fazer esses adaptadores. E a função LIFT é responsável por pegar as funções de "nível inferior" e "atualizá-las" para que funcionem com as funções BIND e também sejam compostas.
Espero ter acertado e, mais importante, espero que o artigo tenha uma visão válida sobre as mônadas. Se nada mais, este artigo ajudou a estimular meu apetite por aprender mais sobre mônadas.
Além das excelentes respostas acima, deixe-me oferecer um link para o seguinte artigo (de Patrick Thomson), que explica as mônadas relacionando o conceito à biblioteca JavaScript jQuery (e sua maneira de usar o "encadeamento de métodos" para manipular o DOM) : jQuery é uma mônada
A documentação do jQuery em si não se refere ao termo "mônada", mas fala sobre o "padrão do construtor", que provavelmente é mais familiar. Isso não muda o fato de que você tem uma mônada adequada lá, talvez sem nem perceber.
Mônadas não são metáforas , mas uma abstração praticamente útil emergindo de um padrão comum, como Daniel Spiewak explica.
Uma mônada é uma maneira de combinar cálculos juntos que compartilham um contexto comum. É como construir uma rede de tubulações. Ao construir a rede, não há dados fluindo através dela. Mas, quando terminei de juntar todos os bits com 'bind' e 'return', invoco algo parecido runMyMonad monad data
e os dados fluem através dos pipes.
Na prática, o monad é uma implementação personalizada do operador de composição de funções que cuida de efeitos colaterais e valores incompatíveis de entrada e retorno (para encadeamento).
Se entendi corretamente, IEnumerable é derivado de mônadas. Gostaria de saber se isso pode ser um ângulo de abordagem interessante para nós do mundo C #.
Pelo que vale, aqui estão alguns links para tutoriais que me ajudaram (e não, ainda não entendi o que são mônadas).
As duas coisas que me ajudaram a aprender melhor foram:
Capítulo 8, "Analisadores Funcionais", do livro de Graham Hutton, Programming in Haskell . Isso não menciona mônadas, na verdade, mas se você puder ler o capítulo e realmente entender tudo nele, principalmente como uma sequência de operações de ligação é avaliada, você entenderá o interior das mônadas. Espere que isso faça várias tentativas.
O tutorial Tudo sobre mônadas . Isso fornece vários bons exemplos de uso deles, e devo dizer que a analogia no Appendex funcionou para mim.
O Monoid parece ser algo que garante que todas as operações definidas em um Monoid e em um tipo suportado sempre retornem um tipo suportado dentro do Monoid. Por exemplo, Qualquer número + Qualquer número = Um número, sem erros.
Considerando que divisão aceita dois fracionários e retorna um fracionário, que definiu divisão por zero como Infinito em haskell de alguma forma (que por acaso é um fracionário de alguma forma) ...
De qualquer forma, parece que as mônadas são apenas uma maneira de garantir que sua cadeia de operações se comporte de maneira previsível, e uma função que afirma ser Num -> Num, composta por outra função de Num-> Num chamada com x, não digamos, atire nos mísseis.
Por outro lado, se temos uma função que dispara os mísseis, podemos compor com outras funções que também disparam os mísseis, porque nossa intenção é clara - queremos disparar os mísseis - mas não tentamos imprimindo "Hello World" por algum motivo estranho.
Em Haskell, main é do tipo IO (), ou IO [()], a distiction é estranha e não discutirei, mas eis o que acho que acontece:
Se eu tiver main, quero que ele execute uma cadeia de ações, a razão pela qual eu executo o programa é produzir um efeito - geralmente através de IO. Assim, eu posso encadear operações de IO em conjunto, principalmente para - fazer IO, nada mais.
Se eu tentar fazer algo que não "retorne IO", o programa reclamará que a cadeia não flui, ou basicamente "Como isso se relaciona com o que estamos tentando fazer - uma ação de IO", parece forçar o programador manter sua linha de pensamento, sem se desviar e pensar em disparar os mísseis, enquanto cria algoritmos para classificação - o que não flui.
Basicamente, as mônadas parecem ser uma dica para o compilador de que "ei, você conhece essa função que retorna um número aqui, ela nem sempre funciona, às vezes pode produzir um número e às vezes nada, apenas mantenha isso em mente". Sabendo disso, se você tentar afirmar uma ação monádica, a ação monádica poderá funcionar como uma exceção de tempo de compilação dizendo "ei, isso não é realmente um número, isso PODE ser um número, mas você não pode assumir isso, faça algo para garantir que o fluxo seja aceitável ". o que impede o comportamento imprevisível do programa - em uma extensão justa.
Parece que as mônadas não são sobre pureza, nem controle, mas sobre a manutenção de uma identidade de uma categoria na qual todo comportamento é previsível e definido, ou não é compilado. Você não pode fazer nada quando se espera que faça algo e não pode fazer algo se se espera que não faça nada (visível).
A maior razão que eu pude pensar para o Monads é - vá para o código Procedural / OOP, e você perceberá que não sabe onde o programa começa nem termina, tudo o que você vê é muito salto e muita matemática , mágica e mísseis. Você não será capaz de mantê-lo e, se puder, gastará muito tempo pensando em todo o programa antes de entender qualquer parte dele, porque a modularidade nesse contexto é baseada em "seções" interdependentes do código, em que o código é otimizado para ser o mais relacionado possível, com a promessa de eficiência / inter-relação. As mônadas são muito concretas e bem definidas por definição, e garantem que o fluxo do programa seja possível de analisar e isolar partes difíceis de analisar - como elas próprias são mônadas. Uma mônada parece ser uma " ou destruir o universo ou até distorcer o tempo - não temos idéia nem garantia de que É O QUE É. Uma mônada garante que é o que é. o que é muito poderoso. ou destruir o universo ou até distorcer o tempo - não temos idéia nem garantia de que É O QUE É. Uma mônada garante que é o que é. o que é muito poderoso.
Todas as coisas no "mundo real" parecem ser mônadas, no sentido de que estão vinculadas por leis definidas e observáveis que impedem a confusão. Isso não significa que precisamos imitar todas as operações desse objeto para criar classes; em vez disso, podemos simplesmente dizer "um quadrado é um quadrado", nada além de um quadrado, nem mesmo um retângulo nem um círculo, e "um quadrado tem área do comprimento de uma de suas dimensões existentes multiplicado por ele mesmo.Não importa qual quadrado você tenha, se for um quadrado no espaço 2D, sua área não pode ser outra coisa senão seu comprimento ao quadrado, é quase trivial provar. não precisamos fazer afirmações para garantir que nosso mundo seja o que é, apenas usamos implicações da realidade para impedir que nossos programas caiam no rumo.
Estou praticamente garantido que estou errado, mas acho que isso poderia ajudar alguém lá fora, por isso espero que ajude alguém.
No contexto do Scala, você encontrará a definição a seguir mais simples. Basicamente, o flatMap (ou bind) é 'associativo' e existe uma identidade.
trait M[+A] {
def flatMap[B](f: A => M[B]): M[B] // AKA bind
// Pseudo Meta Code
def isValidMonad: Boolean = {
// for every parameter the following holds
def isAssociativeOn[X, Y, Z](x: M[X], f: X => M[Y], g: Y => M[Z]): Boolean =
x.flatMap(f).flatMap(g) == x.flatMap(f(_).flatMap(g))
// for every parameter X and x, there exists an id
// such that the following holds
def isAnIdentity[X](x: M[X], id: X => M[X]): Boolean =
x.flatMap(id) == x
}
}
Por exemplo
// These could be any functions
val f: Int => Option[String] = number => if (number == 7) Some("hello") else None
val g: String => Option[Double] = string => Some(3.14)
// Observe these are identical. Since Option is a Monad
// they will always be identical no matter what the functions are
scala> Some(7).flatMap(f).flatMap(g)
res211: Option[Double] = Some(3.14)
scala> Some(7).flatMap(f(_).flatMap(g))
res212: Option[Double] = Some(3.14)
// As Option is a Monad, there exists an identity:
val id: Int => Option[Int] = x => Some(x)
// Observe these are identical
scala> Some(7).flatMap(id)
res213: Option[Int] = Some(7)
scala> Some(7)
res214: Some[Int] = Some(7)
NOTA Estritamente falando, a definição de uma Mônada na programação funcional não é a mesma que a definição de uma Mônada na Teoria das Categorias , que é definida nos turnos de map
e flatten
. Embora eles sejam equivalentes sob certos mapeamentos. Esta apresentação é muito boa: http://www.slideshare.net/samthemonad/monad-presentation-scala-as-a-category
Essa resposta começa com um exemplo motivador, funciona através do exemplo, deriva um exemplo de mônada e define formalmente "mônada".
Considere estas três funções no pseudocódigo:
f(<x, messages>) := <x, messages "called f. ">
g(<x, messages>) := <x, messages "called g. ">
wrap(x) := <x, "">
f
pega um par ordenado do formulário <x, messages>
e retorna um par ordenado. Ele deixa o primeiro item intocado e anexa "called f. "
ao segundo item. O mesmo com g
.
Você pode compor essas funções e obter seu valor original, juntamente com uma sequência que mostra em qual ordem as funções foram chamadas:
f(g(wrap(x)))
= f(g(<x, "">))
= f(<x, "called g. ">)
= <x, "called g. called f. ">
Você não gosta do fato de que f
e g
são responsáveis para acrescentar suas próprias mensagens de log para as informações de registro anterior. (Imaginem por causa do argumento de que, em vez de acrescentar cordas, f
e g
deve executar lógica complicado no segundo item do par Seria uma dor de repetição que a lógica complicada em dois -. Ou mais -. Funções diferentes)
Você prefere escrever funções mais simples:
f(x) := <x, "called f. ">
g(x) := <x, "called g. ">
wrap(x) := <x, "">
Mas veja o que acontece quando você os compõe:
f(g(wrap(x)))
= f(g(<x, "">))
= f(<<x, "">, "called g. ">)
= <<<x, "">, "called g. ">, "called f. ">
O problema é que passar um par para uma função não fornece o que você deseja. Mas e se você pudesse alimentar um par em uma função:
feed(f, feed(g, wrap(x)))
= feed(f, feed(g, <x, "">))
= feed(f, <x, "called g. ">)
= <x, "called g. called f. ">
Leia feed(f, m)
como "feed m
into f
". Para alimentar um par <x, messages>
em uma função f
é passar x
em f
, ficar <y, message>
fora de f
, e retornar <y, messages message>
.
feed(f, <x, messages>) := let <y, message> = f(x)
in <y, messages message>
Observe o que acontece quando você faz três coisas com suas funções:
Primeiro: se você quebrar um valor e depois alimentar o par resultante em uma função:
feed(f, wrap(x))
= feed(f, <x, "">)
= let <y, message> = f(x)
in <y, "" message>
= let <y, message> = <x, "called f. ">
in <y, "" message>
= <x, "" "called f. ">
= <x, "called f. ">
= f(x)
É o mesmo que passar o valor para a função.
Segundo: se você alimentar um par em wrap
:
feed(wrap, <x, messages>)
= let <y, message> = wrap(x)
in <y, messages message>
= let <y, message> = <x, "">
in <y, messages message>
= <x, messages "">
= <x, messages>
Isso não muda o par.
Terceiro: se você definir uma função que recebe x
e alimenta g(x)
em f
:
h(x) := feed(f, g(x))
e alimente um par:
feed(h, <x, messages>)
= let <y, message> = h(x)
in <y, messages message>
= let <y, message> = feed(f, g(x))
in <y, messages message>
= let <y, message> = feed(f, <x, "called g. ">)
in <y, messages message>
= let <y, message> = let <z, msg> = f(x)
in <z, "called g. " msg>
in <y, messages message>
= let <y, message> = let <z, msg> = <x, "called f. ">
in <z, "called g. " msg>
in <y, messages message>
= let <y, message> = <x, "called g. " "called f. ">
in <y, messages message>
= <x, messages "called g. " "called f. ">
= feed(f, <x, messages "called g. ">)
= feed(f, feed(g, <x, messages>))
É o mesmo que alimentar o par g
e alimentar o par resultante f
.
Você tem mais de uma mônada. Agora você só precisa saber sobre os tipos de dados no seu programa.
Que tipo de valor é <x, "called f. ">
? Bem, isso depende de que tipo de valor x
é. Se x
for do tipo t
, então seu par é um valor do tipo "par de t
e sequência". Ligue para esse tipo M t
.
M
é um construtor de tipos: M
sozinho não se refere a um tipo, mas M _
se refere a um tipo depois que você preencher o espaço em branco com um tipo. An M int
é um par de um int e uma string. An M string
é um par de uma string e uma string. Etc.
Parabéns, você criou uma mônada!
Formalmente, sua mônada é a tupla <M, feed, wrap>
.
Uma mônada é uma tupla em <M, feed, wrap>
que:
M
é um construtor de tipos.feed
pega uma (função que pega t
ae retorna an M u
) e an M t
e retorna an M u
.wrap
pega um v
e retorna um M v
.t
,, u
e v
existem três tipos que podem ou não ser iguais. Uma mônada satisfaz as três propriedades que você provou para sua mônada específica:
Alimentar um embrulhado t
em uma função é o mesmo que passar o desembrulhado t
para a função.
Formalmente: feed(f, wrap(x)) = f(x)
Alimentando um M t
em wrap
nada contribui para a M t
.
Formalmente: feed(wrap, m) = m
Alimentando um M t
(chame m
) em uma função que
t
parag
M u
(chame n
) deg
n
emf
é o mesmo que
m
emg
n
deg
n
emf
Formalmente: feed(h, m) = feed(f, feed(g, m))
ondeh(x) := feed(f, g(x))
Normalmente, feed
é chamado bind
(AKA >>=
em Haskell) e wrap
é chamado return
.
Vou tentar explicar Monad
no contexto de Haskell.
Na programação funcional, a composição da função é importante. Ele permite que nosso programa seja composto por funções pequenas e fáceis de ler.
Digamos que temos duas funções: g :: Int -> String
e f :: String -> Bool
.
Nós podemos fazer (f . g) x
, que é exatamente o mesmo que f (g x)
, onde x
é um Int
valor.
Ao fazer a composição / aplicar o resultado de uma função a outra, é importante ter os tipos correspondentes. No caso acima, o tipo do resultado retornado por g
deve ser o mesmo que o tipo aceito por f
.
Mas, às vezes, os valores estão em contextos, e isso torna um pouco menos fácil alinhar os tipos. (Ter valores em contextos é muito útil. Por exemplo, o Maybe Int
tipo representa um Int
valor que pode não estar lá, o IO String
tipo representa um String
valor que existe como resultado da execução de alguns efeitos colaterais.)
Digamos que agora temos g1 :: Int -> Maybe String
e f1 :: String -> Maybe Bool
. g1
e f1
são muito semelhantes g
e f
respectivamente.
Não podemos fazer (f1 . g1) x
ou f1 (g1 x)
, onde x
está um Int
valor. O tipo do resultado retornado por g1
não é o que f1
espera.
Poderíamos compor f
e g
com o .
operador, mas agora não podemos compor f1
e g1
com .
. O problema é que não podemos passar diretamente um valor em um contexto para uma função que espera um valor que não esteja em um contexto.
Não seria bom se apresentássemos um operador para compor g1
e f1
, para que possamos escrever (f1 OPERATOR g1) x
? g1
retorna um valor em um contexto. O valor será retirado do contexto e aplicado f1
. E sim, temos um operador assim. É <=<
.
Também temos o >>=
operador que faz exatamente a mesma coisa para nós, embora em uma sintaxe ligeiramente diferente.
Nós escrevemos: g1 x >>= f1
. g1 x
é um Maybe Int
valor. O >>=
operador ajuda a tirar esse Int
valor do contexto "talvez não esteja lá" e aplicá-lo f1
. O resultado de f1
, que é a Maybe Bool
, será o resultado de toda a >>=
operação.
E, finalmente, por que é Monad
útil? Uma vez que Monad
é a classe de tipo que define o >>=
operador, muito da mesma forma como a Eq
classe de tipo que define a ==
e /=
operadores.
Para concluir, a Monad
classe type define o >>=
operador que nos permite passar valores em um contexto (chamamos esses valores monádicos) a funções que não esperam valores em um contexto. O contexto será tratado.
Se há uma coisa a lembrar aqui, é que Monad
permite a composição de funções que envolve valores em contextos .
{-# LANGUAGE InstanceSigs #-}
newtype Id t = Id t
instance Monad Id where
return :: t -> Id t
return = Id
(=<<) :: (a -> Id b) -> Id a -> Id b
f =<< (Id x) = f x
O operador $
de aplicação de funções
forall a b. a -> b
é definido canonicamente
($) :: (a -> b) -> a -> b
f $ x = f x
infixr 0 $
em termos de aplicação da função primitiva de Haskell f x
( infixl 10
).
Composição .
é definido em termos de $
como
(.) :: (b -> c) -> (a -> b) -> (a -> c)
f . g = \ x -> f $ g x
infixr 9 .
e satisfaz as equivalências forall f g h.
f . id = f :: c -> d Right identity
id . g = g :: b -> c Left identity
(f . g) . h = f . (g . h) :: a -> d Associativity
.
é associativo e id
é sua identidade direita e esquerda.
Na programação, uma mônada é um construtor do tipo functor com uma instância da classe do tipo mônada. Existem várias variantes equivalentes de definição e implementação, cada uma carregando intuições ligeiramente diferentes sobre a abstração da mônada.
Um functor é um tipo de construtor f
de tipo * -> *
com uma instância da classe de tipo de functor.
{-# LANGUAGE KindSignatures #-}
class Functor (f :: * -> *) where
map :: (a -> b) -> (f a -> f b)
Além de seguir o protocolo de tipo imposto estaticamente, as instâncias da classe de tipo functor devem obedecer às leis algébricas do functor forall f g.
map id = id :: f t -> f t Identity
map f . map g = map (f . g) :: f a -> f c Composition / short cut fusion
Os cálculos de functor têm o tipo
forall f t. Functor f => f t
Uma computação c r
consiste em resultados r
dentro do contexto c
.
Funções monádicas unárias ou setas Kleisli têm o tipo
forall m a b. Functor m => a -> m b
As setas de Kleisi são funções que recebem um argumento a
e retornam uma computação monádica m b
.
Mônadas são canonicamente definidas em termos do triplo de Kleisli forall m. Functor m =>
(m, return, (=<<))
implementado como a classe de tipo
class Functor m => Monad m where
return :: t -> m t
(=<<) :: (a -> m b) -> m a -> m b
infixr 1 =<<
A identidade Kleisli return
é uma flecha Kleisli que promove um valor t
no contexto monádico m
. O aplicativo Extension ou Kleisli =<<
aplica uma seta Kleisli a -> m b
aos resultados de uma computação m a
.
A composição de Kleisli <=<
é definida em termos de extensão como
(<=<) :: Monad m => (b -> m c) -> (a -> m b) -> (a -> m c)
f <=< g = \ x -> f =<< g x
infixr 1 <=<
<=<
compõe duas setas Kleisli, aplicando a seta esquerda aos resultados da aplicação da seta direita.
Instâncias da classe do tipo mônada devem obedecer às leis da mônada , mais elegantemente declaradas em termos da composição de Kleisli:forall f g h.
f <=< return = f :: c -> m d Right identity
return <=< g = g :: b -> m c Left identity
(f <=< g) <=< h = f <=< (g <=< h) :: a -> m d Associativity
<=<
é associativo e return
é sua identidade direita e esquerda.
O tipo de identidade
type Id t = t
é a função de identidade nos tipos
Id :: * -> *
Interpretado como um functor,
return :: t -> Id t
= id :: t -> t
(=<<) :: (a -> Id b) -> Id a -> Id b
= ($) :: (a -> b) -> a -> b
(<=<) :: (b -> Id c) -> (a -> Id b) -> (a -> Id c)
= (.) :: (b -> c) -> (a -> b) -> (a -> c)
Em Haskell canônico, a mônada de identidade é definida
newtype Id t = Id t
instance Functor Id where
map :: (a -> b) -> Id a -> Id b
map f (Id x) = Id (f x)
instance Monad Id where
return :: t -> Id t
return = Id
(=<<) :: (a -> Id b) -> Id a -> Id b
f =<< (Id x) = f x
Um tipo de opção
data Maybe t = Nothing | Just t
codifica computação Maybe t
que não necessariamente produz um resultado t
, computação que pode "falhar". A opção monad é definida
instance Functor Maybe where
map :: (a -> b) -> (Maybe a -> Maybe b)
map f (Just x) = Just (f x)
map _ Nothing = Nothing
instance Monad Maybe where
return :: t -> Maybe t
return = Just
(=<<) :: (a -> Maybe b) -> Maybe a -> Maybe b
f =<< (Just x) = f x
_ =<< Nothing = Nothing
a -> Maybe b
é aplicado a um resultado apenas se Maybe a
produz um resultado.
newtype Nat = Nat Int
Os números naturais podem ser codificados como números inteiros maiores ou iguais a zero.
toNat :: Int -> Maybe Nat
toNat i | i >= 0 = Just (Nat i)
| otherwise = Nothing
Os números naturais não são fechados sob subtração.
(-?) :: Nat -> Nat -> Maybe Nat
(Nat n) -? (Nat m) = toNat (n - m)
infixl 6 -?
A opção monad cobre uma forma básica de tratamento de exceções.
(-? 20) <=< toNat :: Int -> Maybe Nat
A mônada da lista, sobre o tipo de lista
data [] t = [] | t : [t]
infixr 5 :
e sua operação monóide aditiva "anexa"
(++) :: [t] -> [t] -> [t]
(x : xs) ++ ys = x : xs ++ ys
[] ++ ys = ys
infixr 5 ++
codifica a computação não linear[t]
produzindo uma quantidade natural 0, 1, ...
de resultados t
.
instance Functor [] where
map :: (a -> b) -> ([a] -> [b])
map f (x : xs) = f x : map f xs
map _ [] = []
instance Monad [] where
return :: t -> [t]
return = (: [])
(=<<) :: (a -> [b]) -> [a] -> [b]
f =<< (x : xs) = f x ++ (f =<< xs)
_ =<< [] = []
A extensão =<<
concatena ++
todas as listas [b]
resultantes de aplicativos f x
de uma seta de Kleisli a -> [b]
em elementos de [a]
uma única lista de resultados [b]
.
Deixe os divisores apropriados de um inteiro positivo n
ser
divisors :: Integral t => t -> [t]
divisors n = filter (`divides` n) [2 .. n - 1]
divides :: Integral t => t -> t -> Bool
(`divides` n) = (== 0) . (n `rem`)
então
forall n. let { f = f <=< divisors } in f n = []
Ao definir a classe do tipo mônada, em vez da extensão =<<
, o padrão Haskell usa seu flip, o operador de ligação>>=
.
class Applicative m => Monad m where
(>>=) :: forall a b. m a -> (a -> m b) -> m b
(>>) :: forall a b. m a -> m b -> m b
m >> k = m >>= \ _ -> k
{-# INLINE (>>) #-}
return :: a -> m a
return = pure
Por uma questão de simplicidade, esta explicação usa a hierarquia de classes de tipo
class Functor f
class Functor m => Monad m
Em Haskell, a hierarquia padrão atual é
class Functor f
class Functor p => Applicative p
class Applicative m => Monad m
porque não apenas toda mônada é um functor, mas todo aplicativo é um functor e toda mônada também é um aplicativo.
Usando a mônada da lista, o pseudocódigo imperativo
for a in (1, ..., 10)
for b in (1, ..., 10)
p <- a * b
if even(p)
yield p
traduz aproximadamente para o bloco do ,
do a <- [1 .. 10]
b <- [1 .. 10]
let p = a * b
guard (even p)
return p
a compreensão mônada equivalente ,
[ p | a <- [1 .. 10], b <- [1 .. 10], let p = a * b, even p ]
e a expressão
[1 .. 10] >>= (\ a ->
[1 .. 10] >>= (\ b ->
let p = a * b in
guard (even p) >> -- [ () | even p ] >>
return p
)
)
Compreensões de notação e mônada são açúcar sintático para expressões de ligação aninhadas. O operador de ligação é usado para ligação de nome local dos resultados monádicos.
let x = v in e = (\ x -> e) $ v = v & (\ x -> e)
do { r <- m; c } = (\ r -> c) =<< m = m >>= (\ r -> c)
Onde
(&) :: a -> (a -> b) -> b
(&) = flip ($)
infixl 0 &
A função de guarda está definida
guard :: Additive m => Bool -> m ()
guard True = return ()
guard False = fail
onde o tipo de unidade ou "tupla vazia"
data () = ()
Mônadas aditivas que suportam escolha e falha podem ser abstraídas usando uma classe de tipo
class Monad m => Additive m where
fail :: m t
(<|>) :: m t -> m t -> m t
infixl 3 <|>
instance Additive Maybe where
fail = Nothing
Nothing <|> m = m
m <|> _ = m
instance Additive [] where
fail = []
(<|>) = (++)
onde fail
e <|>
formar um monóideforall k l m.
k <|> fail = k
fail <|> l = l
(k <|> l) <|> m = k <|> (l <|> m)
e fail
é o elemento zero absorvente / aniquilador das mônadas aditivas
_ =<< fail = fail
Se em
guard (even p) >> return p
even p
é verdade, então o guarda produz [()]
e, pela definição de >>
, a função constante local
\ _ -> return p
é aplicado ao resultado ()
. Se falso, o guarda produz a mônada da lista fail
( []
), que não produz resultado para a aplicação de uma flecha de Kleisli >>
; portanto, isso p
é pulado.
Infelizmente, as mônadas são usadas para codificar a computação com estado.
Um processador de estado é uma função
forall st t. st -> (t, st)
que transita um estado st
e produz um resultado t
. O estado st
pode ser qualquer coisa. Nada, bandeira, contagem, matriz, manuseio, máquina, mundo.
O tipo de processadores de estado é geralmente chamado
type State st t = st -> (t, st)
A mônada do processador de estado é o * -> *
functor classificado State st
. As setas Kleisli da mônada do processador de estado são funções
forall st a b. a -> (State st) b
Em Haskell canônico, a versão lenta da mônada do processador de estado é definida
newtype State st t = State { stateProc :: st -> (t, st) }
instance Functor (State st) where
map :: (a -> b) -> ((State st) a -> (State st) b)
map f (State p) = State $ \ s0 -> let (x, s1) = p s0
in (f x, s1)
instance Monad (State st) where
return :: t -> (State st) t
return x = State $ \ s -> (x, s)
(=<<) :: (a -> (State st) b) -> (State st) a -> (State st) b
f =<< (State p) = State $ \ s0 -> let (x, s1) = p s0
in stateProc (f x) s1
Um processador de estado é executado fornecendo um estado inicial:
run :: State st t -> st -> (t, st)
run = stateProc
eval :: State st t -> st -> t
eval = fst . run
exec :: State st t -> st -> st
exec = snd . run
O acesso do estado é fornecido por métodos primitivos get
e put
de abstração sobre mônadas com estado :
{-# LANGUAGE MultiParamTypeClasses, FunctionalDependencies #-}
class Monad m => Stateful m st | m -> st where
get :: m st
put :: st -> m ()
m -> st
declara uma dependência funcional do tipo de estado st
na mônada m
; que a State t
, por exemplo, determinará o tipo de estado como t
único.
instance Stateful (State st) st where
get :: State st st
get = State $ \ s -> (s, s)
put :: st -> State st ()
put s = State $ \ _ -> ((), s)
com o tipo de unidade usado analogamente void
em C.
modify :: Stateful m st => (st -> st) -> m ()
modify f = do
s <- get
put (f s)
gets :: Stateful m st => (st -> t) -> m t
gets f = do
s <- get
return (f s)
gets
é frequentemente usado com acessadores de campo de registro.
O equivalente da mônada do estado da variável threading
let s0 = 34
s1 = (+ 1) s0
n = (* 12) s1
s2 = (+ 7) s1
in (show n, s2)
onde s0 :: Int
é igualmente referencialmente transparente, mas infinitamente mais elegante e prático
(flip run) 34
(do
modify (+ 1)
n <- gets (* 12)
modify (+ 7)
return (show n)
)
modify (+ 1)
é um cálculo do tipo State Int ()
, exceto pelo efeito equivalente a return ()
.
(flip run) 34
(modify (+ 1) >>
gets (* 12) >>= (\ n ->
modify (+ 7) >>
return (show n)
)
)
A lei mônada da associatividade pode ser escrita em termos de >>=
forall m f g.
(m >>= f) >>= g = m >>= (\ x -> f x >>= g)
ou
do { do { do {
r1 <- do { x <- m; r0 <- m;
r0 <- m; = do { = r1 <- f r0;
f r0 r1 <- f x; g r1
}; g r1 }
g r1 }
} }
Como na programação orientada a expressões (por exemplo, Rust), a última declaração de um bloco representa seu rendimento. O operador de ligação às vezes é chamado de "ponto e vírgula programável".
As primitivas da estrutura de controle de iteração da programação imperativa estruturada são emuladas monadicamente
for :: Monad m => (a -> m b) -> [a] -> m ()
for f = foldr ((>>) . f) (return ())
while :: Monad m => m Bool -> m t -> m ()
while c m = do
b <- c
if b then m >> while c m
else return ()
forever :: Monad m => m t
forever m = m >> forever m
data World
A mônada do processador de estado mundial de E / S é uma reconciliação do puro Haskell e do mundo real, da semântica operacional denotativa e imperativa funcional. Um análogo próximo da implementação estrita real:
type IO t = World -> (t, World)
A interação é facilitada por primitivas impuras
getChar :: IO Char
putChar :: Char -> IO ()
readFile :: FilePath -> IO String
writeFile :: FilePath -> String -> IO ()
hSetBuffering :: Handle -> BufferMode -> IO ()
hTell :: Handle -> IO Integer
. . . . . .
A impureza do código que usa IO
primitivas é permanentemente protocolada pelo sistema de tipos. Porque a pureza é incrível, o que acontece IO
, permanece IO
.
unsafePerformIO :: IO t -> t
Ou, pelo menos, deveria.
A assinatura de tipo de um programa Haskell
main :: IO ()
main = putStrLn "Hello, World!"
expande para
World -> ((), World)
Uma função que transforma um mundo.
A categoria cujos objetos são do tipo Haskell e quais dos morfismos são funções entre os tipos de Haskell é, “rápido e solto”, a categoria Hask
.
Um functor T
é um mapeamento de uma categoria C
para uma categoria D
; para cada objeto em C
um objeto emD
Tobj : Obj(C) -> Obj(D)
f :: * -> *
e para cada morfismo em C
um morfismo emD
Tmor : HomC(X, Y) -> HomD(Tobj(X), Tobj(Y))
map :: (a -> b) -> (f a -> f b)
onde X
, Y
são objetos C
. HomC(X, Y)
é a classe de homomorfismo de todos os morfismos X -> Y
em C
. O functor deve preservar a identidade e composição do morfismo, a "estrutura" de C
, em D
.
Tmor Tobj
T(id) = id : T(X) -> T(X) Identity
T(f) . T(g) = T(f . g) : T(X) -> T(Z) Composition
A categoria Kleisli de uma categoria C
é dada por um triplo Kleisli
<T, eta, _*>
de um endofuncor
T : C -> C
( f
), um morfismo de identidade eta
( return
) e um operador de extensão *
( =<<
).
Cada morfismo Kleisli em Hask
f : X -> T(Y)
f :: a -> m b
pelo operador de extensão
(_)* : Hom(X, T(Y)) -> Hom(T(X), T(Y))
(=<<) :: (a -> m b) -> (m a -> m b)
recebe um morfismo na Hask
categoria Kleisli de
f* : T(X) -> T(Y)
(f =<<) :: m a -> m b
A composição na categoria Kleisli .T
é dada em termos de extensão
f .T g = f* . g : X -> T(Z)
f <=< g = (f =<<) . g :: a -> m c
e satisfaz os axiomas da categoria
eta .T g = g : Y -> T(Z) Left identity
return <=< g = g :: b -> m c
f .T eta = f : Z -> T(U) Right identity
f <=< return = f :: c -> m d
(f .T g) .T h = f .T (g .T h) : X -> T(U) Associativity
(f <=< g) <=< h = f <=< (g <=< h) :: a -> m d
aplicando as transformações de equivalência
eta .T g = g
eta* . g = g By definition of .T
eta* . g = id . g forall f. id . f = f
eta* = id forall f g h. f . h = g . h ==> f = g
(f .T g) .T h = f .T (g .T h)
(f* . g)* . h = f* . (g* . h) By definition of .T
(f* . g)* . h = f* . g* . h . is associative
(f* . g)* = f* . g* forall f g h. f . h = g . h ==> f = g
em termos de extensão são canonicamente dadas
eta* = id : T(X) -> T(X) Left identity
(return =<<) = id :: m t -> m t
f* . eta = f : Z -> T(U) Right identity
(f =<<) . return = f :: c -> m d
(f* . g)* = f* . g* : T(X) -> T(Z) Associativity
(((f =<<) . g) =<<) = (f =<<) . (g =<<) :: m a -> m c
Mônadas também podem ser definidas em termos não da extensão kleisliana, mas de uma transformação natural mu
, na programação chamada join
. Uma mônada é definida em termos de mu
triplo sobre uma categoria C
, de um endofuncor
T : C -> C
f :: * -> *
e duas tranformações naturais
eta : Id -> T
return :: t -> f t
mu : T . T -> T
join :: f (f t) -> f t
satisfazendo as equivalências
mu . T(mu) = mu . mu : T . T . T -> T . T Associativity
join . map join = join . join :: f (f (f t)) -> f t
mu . T(eta) = mu . eta = id : T -> T Identity
join . map return = join . return = id :: f t -> f t
A classe do tipo mônada é então definida
class Functor m => Monad m where
return :: t -> m t
join :: m (m t) -> m t
A mu
implementação canônica da opção mônada:
instance Monad Maybe where
return = Just
join (Just m) = m
join Nothing = Nothing
A concat
função
concat :: [[a]] -> [a]
concat (x : xs) = x ++ concat xs
concat [] = []
é o join
da mônada da lista.
instance Monad [] where
return :: t -> [t]
return = (: [])
(=<<) :: (a -> [b]) -> ([a] -> [b])
(f =<<) = concat . map f
As implementações de join
podem ser traduzidas do formulário de extensão usando a equivalência
mu = id* : T . T -> T
join = (id =<<) :: m (m t) -> m t
A conversão reversa do mu
formulário para extensão é dada por
f* = mu . T(f) : T(X) -> T(Y)
(f =<<) = join . map f :: m a -> m b
Philip Wadler: Mônadas para programação funcional
Simon L Peyton Jones, Philip Wadler: programação funcional imperativa
Jonathan MD Hill, Keith Clarke: Uma introdução à teoria da categoria, mônadas da teoria da categoria e sua relação com a programação funcional ´
Eugenio Moggi: Noções de computação e mônadas
Mas por que uma teoria tão abstrata deve ter alguma utilidade para a programação?
A resposta é simples: como cientistas da computação, valorizamos a abstração ! Quando projetamos a interface para um componente de software, queremos que ele revele o mínimo possível sobre a implementação. Queremos poder substituir a implementação por muitas alternativas, muitas outras 'instâncias' do mesmo 'conceito'. Quando projetamos uma interface genérica para muitas bibliotecas de programas, é ainda mais importante que a interface que escolhemos tenha uma variedade de implementações. É a generalidade do conceito de mônada que valorizamos tanto, porque a teoria das categorias é tão abstrata que seus conceitos são tão úteis para a programação.
Não é de surpreender, portanto, que a generalização das mônadas que apresentamos abaixo também tenha uma estreita conexão com a teoria das categorias. Mas enfatizamos que nosso objetivo é muito prático: não é "implementar a teoria das categorias", é encontrar uma maneira mais geral de estruturar as bibliotecas combinadoras. É simplesmente nossa boa sorte que os matemáticos já tenham feito grande parte do trabalho por nós!
de Generalizar mônadas a flechas por John Hughes
O que o mundo precisa é de outro post de mônada, mas acho que isso é útil na identificação de mônadas existentes na natureza.
O acima é um fractal chamado triângulo de Sierpinski, o único fractal que me lembro de desenhar. Os fractais são estruturas auto-semelhantes, como o triângulo acima, em que as partes são semelhantes ao todo (neste caso, exatamente metade da escala do triângulo pai).
Mônadas são fractais. Dada uma estrutura de dados monádica, seus valores podem ser compostos para formar outro valor da estrutura de dados. É por isso que é útil para a programação, e é por isso que ocorre em muitas situações.
http://code.google.com/p/monad-tutorial/ é um trabalho em andamento para abordar exatamente essa questão.
Deixe o " {| a |m}
" abaixo representar alguns dados monádicos. Um tipo de dados que anuncia um a
:
(I got an a!)
/
{| a |m}
A função, f
sabe como criar uma mônada, se ela tivesse a
:
(Hi f! What should I be?)
/
(You?. Oh, you'll be /
that data there.) /
/ / (I got a b.)
| -------------- |
| / |
f a |
|--later-> {| b |m}
Aqui vemos a função f
,, tenta avaliar uma mônada, mas é repreendida.
(Hmm, how do I get that a?)
o (Get lost buddy.
o Wrong type.)
o /
f {| a |m}
Funtion,, f
encontra uma maneira de extrair o a
usando >>=
.
(Muaahaha. How you
like me now!?)
(Better.) \
| (Give me that a.)
(Fine, well ok.) |
\ |
{| a |m} >>= f
Pouco f
sabe, a mônada e >>=
estão em conluio.
(Yah got an a for me?)
(Yeah, but hey |
listen. I got |
something to |
tell you first |
...) \ /
| /
{| a |m} >>= f
Mas sobre o que eles realmente falam? Bem, isso depende da mônada. Falar apenas no resumo tem uso limitado; você precisa ter alguma experiência com mônadas específicas para aprofundar o entendimento.
Por exemplo, o tipo de dados Talvez
data Maybe a = Nothing | Just a
tem uma instância de mônada que age da seguinte forma ...
Onde, se for o caso Just a
(Yah what is it?)
(... hm? Oh, |
forget about it. |
Hey a, yr up.) |
\ |
(Evaluation \ |
time already? \ |
Hows my hair?) | |
| / |
| (It's |
| fine.) /
| / /
{| a |m} >>= f
Mas para o caso de Nothing
(Yah what is it?)
(... There |
is no a. ) |
| (No a?)
(No a.) |
| (Ok, I'll deal
| with this.)
\ |
\ (Hey f, get lost.)
\ | ( Where's my a?
\ | I evaluate a)
\ (Not any more |
\ you don't. |
| We're returning
| Nothing.) /
| | /
| | /
| | /
{| a |m} >>= f (I got a b.)
| (This is \
| such a \
| sham.) o o \
| o|
|--later-> {| b |m}
Portanto, a mônada Maybe permite que uma computação continue se ela realmente contiver a a
publicidade, mas interrompe a computação se não contiver . O resultado, no entanto, ainda é um pedaço de dados monádicos, embora não seja a saída de f
. Por esse motivo, a mônada Maybe é usada para representar o contexto de falha.
Mônadas diferentes se comportam de maneira diferente. Listas são outros tipos de dados com instâncias monádicas. Eles se comportam da seguinte maneira:
(Ok, here's your a. Well, its
a bunch of them, actually.)
|
| (Thanks, no problem. Ok
| f, here you go, an a.)
| |
| | (Thank's. See
| | you later.)
| (Whoa. Hold up f, |
| I got another |
| a for you.) |
| | (What? No, sorry.
| | Can't do it. I
| | have my hands full
| | with all these "b"
| | I just made.)
| (I'll hold those, |
| you take this, and /
| come back for more /
| when you're done /
| and we'll do it /
| again.) /
\ | ( Uhhh. All right.)
\ | /
\ \ /
{| a |m} >>= f
Nesse caso, a função sabia como fazer uma lista a partir de sua entrada, mas não sabia o que fazer com entradas e listas extras. O vínculo >>=
, ajudado f
pela combinação de várias saídas. Incluo este exemplo para mostrar que, embora >>=
seja responsável pela extração a
, ele também tem acesso à eventual saída vinculada de f
. De fato, ele nunca extrairá nenhum, a a
menos que saiba que a saída final tem o mesmo tipo de contexto.
Existem outras mônadas usadas para representar diferentes contextos. Aqui estão algumas caracterizações de mais alguns. A IO
mônada na verdade não tem a
, mas conhece um cara e vai conseguir isso a
para você. A State st
mônada tem um estoque secreto do st
qual passará para f
debaixo da mesa, mesmo que f
apenas tenha pedido uma a
. A Reader r
mônada é semelhante a State st
, embora só permita f
ver r
.
O ponto em tudo isso é que qualquer tipo de dado declarado como Mônada está declarando algum tipo de contexto para extrair um valor da Mônada. O grande ganho de tudo isso? Bem, é fácil o suficiente para calcular um cálculo com algum tipo de contexto. No entanto, pode ficar confuso ao reunir vários cálculos carregados de contexto. As operações da mônada cuidam de resolver as interações do contexto para que o programador não precise.
Observe que esse uso >>=
facilita a bagunça, afastando parte da autonomia f
. Ou seja, no caso acima, Nothing
por exemplo, f
não consegue mais decidir o que fazer no caso de Nothing
; está codificado >>=
. Essa é a troca. Se fosse necessário f
decidir o que fazer no caso de Nothing
, f
deveria ter sido uma função de Maybe a
para Maybe b
. Nesse caso, Maybe
ser mônada é irrelevante.
Observe, no entanto, que às vezes um tipo de dados não exporta seus construtores (olhando para o IO) e, se queremos trabalhar com o valor anunciado, temos poucas opções, a não ser trabalhar com sua interface monádica.
Uma mônada é uma coisa usada para encapsular objetos que mudam de estado. É frequentemente encontrado em idiomas que, de outra forma, não permitem que você tenha um estado modificável (por exemplo, Haskell).
Um exemplo seria para E / S de arquivo.
Você seria capaz de usar uma mônada para E / S de arquivo para isolar a natureza do estado de alteração apenas no código que usava a mônada. O código dentro da Mônada pode efetivamente ignorar a mudança do estado do mundo fora da Mônada - isso facilita muito o raciocínio sobre o efeito geral do seu programa.